Jabber D E V E L O P E R ’ S
H A N D B O O K
Jabber D E V E L O P E R ’ S
H A N D B O O K
Dana Moore William Wright
DEVELOPER’S LIBRARY
Sams Publishing, 800 East 96th Street, Indianapolis, Indiana 46240
Jabber Developer’s Handbook Copyright © 2004 by Sams Publishing
Acquisitions Editor Kathryn Mohr
All rights reserved. No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any means, electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher. No patent liability is assumed with respect to the use of the information contained herein. Although every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions. Nor is any liability assumed for damages resulting from the use of the information contained herein.
Development Editor Scott Meyers
International Standard Book Number: 0-672-32536-5
Indexer Erika Millen
Library of Congress Catalog Card Number: 752063325360 Printed in the United States of America First Printing: July 2003 06 05 04 03
4 3 2 1
Trademarks All terms mentioned in this book that are known to be trademarks or service marks have been appropriately capitalized. Sams Publishing cannot attest to the accuracy of this information. Use of a term in this book should not be regarded as affecting the validity of any trademark or service mark.
Warning and Disclaimer Every effort has been made to make this book as complete and as accurate as possible, but no warranty or fitness is implied.The information provided is on an “as is” basis.
Bulk Sales Sams Publishing offers excellent discounts on this book when ordered in quantity for bulk purchases or special sales. For more information, please contact: U.S. Corporate and Government Sales 1-800-382-3419
[email protected] For sales outside of the U.S., please contact: International Sales +1-317-581-3793
[email protected]
Managing Editor Charlotte Clapp Project Editor George E. Nedeff Copy Editor Margo Catts
Proofreader Tracy Donhardt Technical Editor Ryan Eatman Team Coordinator Vanessa Evans Multimedia Developer Dan Scherf Designer Gary Adair Page Layout Bronkella Publishing
❖ For my wife, Karan, with appreciation for your tolerance of all those days and nights I spent holed up working on this book. —WW ❖ For Walter J.Yourell, who is for me the greatest member of the “Greatest Generation,” and for my wife, Jane, whose long-suffering forbearance and feigned interest in arcane points of Jabber architecture explained to her in the wee hours are duly noted and cherished. —DM ❖
Contents at a Glance I Jabber In-Depth 1 What Is Jabber Technology? 7 2 Installing and Configuring Jabber Software 35 3 All About Jabber Clients 69 4 Jabber Server Architecture 123 5 Extending the Jabber Server 155 6 Jabber Security 225 II Jabber-Based Networked Applications 7 Jabber and Web Services 273 8 Jabber and Conversational Software Agents 299 9 Jabber and System Control and Administration 327 10 Jabber and JXTA 365 11 Jabber Libraries for Popular Languages 399 III Appendixes A Glossary 431 B XML Basics 435 C Resources 439 Index 443
Table of Contents 1 Introduction: Let’s Jabber 1 Jabber Enables Peer-to-Peer Computing 1 Jabber Enables an Evolved System Architecture 2
I Jabber In-Depth 5 1 What Is Jabber Technology? 7 Traditional Applications and How They Got That Way 7 The Nature of Traditional Tools 8 Shortcomings of Traditional Tools 9 Jabber Is… 11 …Built-in Services… 11 Service Discovery 13 Server and Client Interactions 14 …Client-Based Services… 17 …Open… 19 …Asynchronous… 19 …Extensible… 20 …Decentralized… 21 …Secure… 22 …XML Protocol… 22 Riding on TCP/IP 22 Provides Real-time Exchange of Messages 22 …And Presence Information 23 …Between Two Endpoints on the Open Internet 23 …And a Corporate Intranet 24 A Useful Application to Jump-Start Your Interest 24 A Practical User Creation Script (Java version) 25 A Practical User Creation Script (Python version) 30 Jabber’s Open Source Development Heritage (And Its Implications) 33 Conclusion (or Rather,The Beginning) 33
viii
Contents
2 Installing and Configuring Jabber Software 35 Downloading the Server Software 35 Installing the Server Software 36 Linux and Unix 36 Windows 39 Initial Server Configuration 40 Jabberd Command-line Arguments 40 The jabber.xml File 40 Starting jabberd 43 Service Configuration Details 44 Service XDB 44 Service io 46 Service c2s 48 Service s2s 49 Service dnsrv 51 Log Services 51 The Sessions Service 53 Common Optional Services 61 Jabber User Directory (JUD) 61 Conferencing 63 Instant Messaging Clients 66 Summary 68
3 All About Jabber Clients 69 What Is a Jabber Client? 70 Session Mechanics 70 Protocol Mechanics 70 Protocol Details 78 The Tag 78 The Element 78 Jabber Presence 84 Presence Attributes 87 The Element 87 The Element 91 The Subelement 93 Additional Subelements 95
Contents
Using to Convey Arbitrary Data 99 Summary 122
4 Jabber Server Architecture 123 High-Level Architecture 123 Messages and Sessions 125 Remote Messaging 127 Client Initialization 130 Browsable Agents 142 Instant Messaging Gateways 148 Summary 153
5 Extending the Jabber Server 155 A Database Service 156 Configuring the Service 156 Code Listing 158 Connecting to the Server 166 JDBC Survival Skills 167 Browsing 169 Searching 174 A Report Service 179 Code Listing 180 Registration 186 A Small Digression: A Custom Packet Type for JabberBeans 188 Custom Storage in XDB 193 Timed Report Generation 196 An Inventory Management Service 198 Decomposing the Inventory Management Service 208 Summary 224
6 Jabber Security 225 Client Registration 226 Disabling Automatic Registration 231 Registration Data 232
ix
x
Contents
Client Authentication 233 Plain Authentication 236 Digest Authentication 238 Zero-Knowledge Authentication 242 A Custom Authentication Component 245 Connecting the Authentication Service 246 Handling Authorization Packets 247 Using SSL for Client Connections 258 SSL Overview 258 Enabling SSL in the Jabber Server 260 Server-to-Server Connection Authentication (Dialback) 265 Summary 268
II Jabber-Based Networked Applications 271 7 What’s in a Name: Web Services 273 First-Generation Applications: Servers and Glass Terminals 273 Second-Generation Applications: Servers and Clients 274 Third Generation Applications: Enter the Web 275 Fourth-Generation Applications: XML and Web Services 276 XML-RPC 278 SOAP 283 WSDL 284 UDDI 285 Jabber and XML-RPC 286 Jabber-Based RPC 287 Jabber RPC Invoker 287 Jabber RPC Service Provider (First Version) 289 Jabber RPC Service Provider (Second Version) 292 Jabber-RPC Object Lessons 296 Summary 296
Contents
8 Jabber and Conversational Software Agents 299 Motivating This Application 300 Accomplishing the Objective 300 What Is Alice? 300 Why Alice Appears to Work 301 Alice Design Principles 301 The Alicebot Server 302 AIML Folder—Alice’s Built-in Knowledge 302 Lib Folder—Alice ProgramD’s Java Library 303 Alice Configuration Files 303 ALICE Static Knowledge (AIML) Files 306 The Category Tag 306 The Pattern Tag 306 The Template Tag 306 The Topic Tag 306 Fitting the Pieces Together 316 The AliceJabber Mux Code 318 Running ALICE with Jabber 324 Summary 325
9 Jabber and System Control and Administration 327 Jabber for System Event Monitoring 327 Jabber for Version Management 338 Jabber for Distributed Control 341 Jabber for Application Monitoring 350 Summary 363
10 Jabber and JXTA 365 JXTA Technology Introduction 365 Elements of JXTA Technology 366 Identifiers 366 Advertisements 366 Messages 368 Peers 368 Peer Groups 369
xi
xii
Contents
Pipes 369 Modules (Services) 369 Roles for JXTA Peers 370 Rendezvous Peers 370 Relay Peers 370 Proxy Peers 370 Trying Out JXTA 370 The JXTA Java Binding API 375 Example: A Jabber-to-JXTA Bridge 376 Details of the talk Protocol 379 Summary: Jabber and JXTA as Complimentary Technologies 397
11 Jabber Libraries for Popular Languages 399 Jabber-Net—Jabber for the .NET Environment 399 Building the Examples 407 iksemel—Jabber for C/C++ 408 JabberBeans—Jabber for Java 413 JabberPy—Jabber for Python 414 A Cross-Language Example 414 Jabberlib—Jabber for TCL 418 Net::Jabber—Jabber for Perl 421 Jabber4R—Jabber for Ruby 424 Summary 427
III Appendixes 429 A Glossary 431 B XML Basics 435 A Simple Document 435 Tag Attributes 436 Comments 436 XML Namespaces 437 XML Streams 438
Contents
C Resources 439 Jabber Servers 439 Jabber Protocol 439 Jabber Clients 439 Jabber Libraries 440 Macromedia Flash JabberBeans 440 Alicebots 440 JabberPy 440 Miscellany 441
Index 443
440
xiii
About the Authors Dana Moore is a Senior Scientist with BBN Technologies in Arlington,VA. He joined BBN in June 2001 to focus on ULTRA*LOG, a DARPA initiative to build very large-scale Javabased multi-agent societies. Previously, he was Chief Scientist with Roku Technologies, a P2P infrastructure developer, and prior to that, a Distinguished Member of Technical Staff at AT&T Laboratories Research. He is the coauthor of Peer-to-Peer: Building Secure, Scalable, and Manageable Networks. He is a popular conference speaker on software agent systems and various management topics, a university lecturer, and he has contributed articles for numerous computing publications. Moore holds a master of science degree in Technology Management from the University of Maryland, and a bachelor of science in Industrial Design, also from the University of Maryland. William Wright is a Division Engineer with BBN Technologies in Arlington,VA. He provides architecture design and development support for several projects utilizing the Cognitive Agent Architecture (Cougaar) distributed software agent framework. He led the integration and demonstration of one of the world’s largest software agent systems, and led the development of an extension to Cougaar to bring agent technology to embedded systems. He has recently written for Java Developer’s Journal, Dr. Dobb’s Journal, and Embedded Systems Programming magazines. He is coauthor of the book Beginning Java Networking.Wright holds an M.S. in computer science from George Mason University and a Bachelor of Music Education from Indiana University.
Acknowledgments The authors would like to thank our colleague, Rich Kilmer, the author of the Ruby Jabber library, for introducing us to Jabber.We also thank the editors and reviewers for their invaluable help in making this book clear, correct, and readable. We would also like to acknowledge Dr. Mark Greaves and the DARPA Ultralog Program for giving us a testbed for trying out ideas that materially informed both our philosophy and technical approach.
We Want to Hear from You! As the reader of this book, you are our most important critic and commentator.We value your opinion and want to know what we’re doing right, what we could do better, what areas you’d like to see us publish in, and any other words of wisdom you’re willing to pass our way. You can email or write me directly to let me know what you did or didn’t like about this book—as well as what we can do to make our books stronger. Please note that I cannot help you with technical problems related to the topic of this book, and that because of the high volume of mail I receive, I might not be able to reply to every message. When you write, please be sure to include this book’s title and author, as well as your name and phone or email address. I will carefully review your comments and share them with the author and editors who worked on the book. Email:
[email protected] Mail: Mark Taber Associate Publisher Sams Publishing 800 East 96th Street Indianapolis, IN 46240 USA
Reader Services For more information about this book or others from Sams Publishing, visit our Web site at www.samspublishing.com.Type the ISBN (excluding hyphens) or the title of the book in the Search box to find the book you’re looking for.
Introduction: Let’s Jabber
“First principles, Clarice. Simplicity. Read Marcus Aurelius. Of each particular thing, ask, ‘What is it, in itself, what is its nature…?’” Dr. Hannibal Lecter, Silence of the Lambs
I
N LOOKING AT HOW WE COULD BEST SERVE our readers, we had to decide whether to ask you to spend your time wading through the detail of what particular messages look like (and there are many of them) or to focus more on interesting applications. Because the details of message formats are well-documented elsewhere (see Appendixes A and C for resources), and because the debugger capabilities incorporated in many of the freely available IM-style Jabber clients accurately show the XML structure of messages sent and received, we felt that your interests as a developer would be best served if we would give you more interesting examples with lots of working code that you could build on. As developers ourselves, we felt that one good example is worth a thousand words of protocol specification.That is not to say that we won’t cover message structure and protocol—we will. However, we learned Jabber by constructing model applications and observing how they worked, and we suspect that most other developers work this way, too. Frankly, it’s more leverageable to learn the high-level APIs in the JabberBeans (Java) and JabberPy (Python) libraries than to know the precise XML structure of every potential message and message type.
Jabber Enables Peer-to-Peer Computing We will also cover the open-source Jabber server and how it works. Although Jabber makes use of a server between clients, we tend to think of it as enabling an inherently peer-to-peer (P2P) application model. In truth, P2P can take many shapes, some of which have servers in the Net, and some of which don’t. Jabber is, we maintain, very much a way to create P2P applications. In one of the applications we show in Chapter 3, “Jabber Client Protocol,” for example, we show you a P2P style application for photo and MP3 sharing. In Chapter 7, “Jabber and Web Services,” we offer code that shows off Jabber’s capability to tunnel firewalls.
2
Introduction
With its use of Jabber Identifiers as an alternate address space, its capability to transcend firewalls, and its capability to queue messages and create long-lived sessions, Jabber is the very essence of P2P services. In this book, we are specifically interested in presenting Jabber as a P2P protocol for unifying the various pieces of distributed applications and providing applications that demonstrate a preferred way to create architectural patterns such as software agents and Web services.
Jabber Enables an Evolved System Architecture Software paradigms tend to move much more slowly than we think.The World Wide Web, for example, ignored the vast computing power at the end of a network connection, turning the personal computer back into a glass terminal, recapitulating for users the ancient experience of the old 3270 style bi-synchronous terminal. Although the implementation was worlds apart from mainframe computing, the users’ experience had much the same feel. When we think about systems, especially distributed systems 10, 20, 50 years hence, we might therefore imagine that things may be very different, or not change much really. In the last 50 years, we have evolved from single large monolithic systems to loosely coupled architectures that are systems of systems. One genuinely new model has been the notion of “conversational systems,” in which software modules exchange messages with one another. Sometimes the content of these messages may be intended for human participants and sometimes not.What we think of as program modules may start to look like software agents, with request/response semantics transported as messages. Therefore, it may be that conversational systems, transporting richly encoded contents between software elements, offer the most elegant and robust mechanism for solving the toughest programming challenge we face today: constructing robust systems of systems. This may still be true even years from now. A single software process may contain just enough functionality to either service requests from other processes or make requests from other processes. Successful software may therefore involve knitting together the capabilities of bits of code written not as a part of some narrowly focused application but as a contributor to a global software conversation. Every program will exist in a network fabric of other software from which it will get significant parts of its functionality. It is an underlying theme of this book, then, that future systems of systems will indeed be based on a conversational, message-based transport architecture such as Jabber because all network endpoints will look like loosely coupled, highly autonomous, distributed computational entities with conversation-like syntactic/semantic capabilities.You will find concrete examples of computational entities engaged in conversational relationships throughout the sample applications in the book.
Introduction
It has been said that knowledge is what makes the difference between the world you want to create and the world other people think you should have. In this book we combine a healthy dose of knowledge with concrete examples you can use to jump start your own efforts. In Chapter 5, “Extending the Jabber Server,” for example, we show information “push” services implemented with Jabber. In Chapter 8, “Jabber and Conversational Software Agents,” we demonstrate a highly conversational application in which Jabber is fundamental in creating a chat application between a human user and a software agent. In Chapter 11, “Jabber Libraries for Popular Languages,” we demonstrate how Jabber works to extend distributed Internet service architectures. In short, throughout this book, you will find helpful and instructive examples of applications that make use of this new paradigm that you can use as sources of ideas, design elements, and code. We believe that in using the techniques and approaches to application-building we describe, you will be well along a path to building robust and scalable applications and have the knowledge to build a world of applications limited only by your own imagination.
3
I Jabber In-Depth 1
What is Jabber Technology?
2
Installing and Configuring Jabber Software
3
Jabber Client Protocol
4
Jabber Server Architecture
5
Extending the Jabber Server
6
Jabber Security
1 What Is Jabber Technology?
Why did we think we could start clean-slated? The hardest to learn is the least complicated. “Least Complicated,” The Indigo Girls
F
IRST THINGS FIRST.THIS IS A BOOK FOR SOFTWARE developers. As developers, we must admit that we really can’t start clean-slated in embracing a different paradigm than the one in which we are used to programming.We have a certain world view that has been influenced by over two decades of Windows and Macintosh programming and design guidelines.We can not deny that our world view channels our thinking about applications—what they do and what it means to create an application.This book gives you enough knowledge to help you write conversational applications.These are applications that sit on a user’s desktop or on servers on the Net and transcend some of the limitations of traditional software tools, applications that invite a user to interact in a newer and more natural way. Before we can really define Jabber and describe its architecture and show some interesting ways to use it, we should do a quick examination of the strengths and shortcomings of traditional tools and then talk about how applications architected with Jabber can address these shortcomings. It’s frankly the best way for you to get into the proper mindset for writing the kind of applications you’ll be seeing in this book, so let’s get started.
Traditional Applications and How They Got That Way Given our developer biases, we tend to think of applications from two perspectives: as data manipulation tools, and as substitutes for forms, pencil, and paper. From the first
8
Chapter 1
What Is Jabber Technology?
perspective, we recognize that when we create a desktop-style application for a user, we are first and foremost enabling them to interact with a persistent store of some sort. Whether the store contains text and formatting data or something else is immaterial, really.The canonical application’s focus is about creating and manipulating a database of some sort. Even when we move the application to the Internet, we are still thinking in terms of transacting against a data store through a middleware layer. From the second perspective—replacing forms, pencil, and paper with visual fixtures—we create a desktop application that is a collection of two-dimensional representations meant to represent either physical objects, such as buttons that can be pushed, or features on paper forms, such as check boxes and text entry areas. Historically, applications offer a certain look and feel because they were initially intended as direct replacements for paper or mechanical artifacts.Their design center comes from serving the needs of an era of population explosion at the beginning of the twentieth century where meta-data about people had to be reduced to numbers that could be entered by a human clerk onto a paper form, making automation work for the masses of humanity.
The Nature of Traditional Tools The applications we create today owe much to this lineage.We have combined and smoothed the data entry and automation steps, without fundamentally changing the way our application users interact with automation [1]. As a result, the applications we tend to create either for our corporate intranets or for external sale implement these patterns: Direct manipulation.We expect the application’s consumer to use a combination of mouse, keyboard, menus (or equivalent keyboard shortcuts,) on-screen tool representations, and a working area.This working area may contain text or digital photos; it really doesn’t matter. Immediate feedback. If an application does not faithfully echo back the character that was just typed, or display a change in the contents of the picture on which the spray-paint tool was just used, within very tight time constraints, users become very disenchanted with—even distrustful of—the application. Impersonal. Consumers should be able to walk up to any PC hosting the tool, and rely on their (often hard-won) training to manipulate the tool in the same way every time. n
n
n
1 We’re not suggesting that this is a bad thing. For example, writing books today is considerably easier than in the old typewriter era, given the capability to convert WYSIWYG editing pretty much directly into typesetting commands.
The Nature of Traditional Tools
n
Non-coordinative.Tools are rarely expected to act in concert with other tools to achieve some higher use goal.There are seeming exceptions that probe this rule, most notably the Unix convention of chaining tools through pipes.You can set up a multi-party meeting with Microsoft’s Outlook or the like, but we would define PIM tools and tool suites such as Microsoft Office as very large tools rather than open, communicative tools.
Shortcomings of Traditional Tools However, we should be aware that applications have some less desirable anti-patterns[2] as well: They are generally “non-delegated.” That is, it is very hard to instruct a tool or a session-oriented Web page to do something for future use or on a continuing basis.The tool generally has to be opened and in active use by the human to do something useful for the human.There’s no background mode for most tools. Tools are generally “islands of automation.” They usually have no coordinative capability: Alice uses a tool to send Bob a text document via email, but although Alice later revises the document several times, there’s no contextual tool that notices that Alice has forgotten to email Bob any of the revisions. Tools lack a natural model for initiating dialog with a human in a way that can be tolerated by the human. We all curse the notorious “paper clip” model for the help system delivered with a certain tools suite, but are there other ways to make a human aware that some new information is available, but that the human can choose to ignore for the time being? Tools don’t learn much from interactions with a human user. In a famous study at Xerox PARC, researcher Lucy Suchman was trying to figure out whether it would be possible for copier machines to become smarter about what a human user wanted. She concluded in the short-duration sessions between a user and the machine that very little information of value was communicated to the machine; that the machine lacked sufficient vocabulary to understand user intent; and that “mutual constitutions of humans and artifacts do not occur in any singular time and place, nor do they create fixed human/artifact relations.” In short, the domain of a given tool is so fixed and narrow that it can’t likely get enough time with a human to deliver much enduring value. n
n
n
n
2 An AntiPattern is a software pattern that defines going from a problem to a bad solution. Bad solutions may be widely embraced for a variety of reasons—anything from dominance of a major industry proponent to designing around hardware assumptions that are no longer valid. Because AntiPatterns are often so embedded in our thinking, they often are not even recognized.
9
10
Chapter 1
What Is Jabber Technology?
A large reason for these anti-patterns is that it’s hard to build tools with the vocabulary to perform services for us that we could conceivably ask another human to do for us. Suppose that we could conceive of an application architecture that could address some of these serious anti-patterns. Suppose that we could develop to a standard that Is platform- and language-agnostic. Is message-based, expressed in an open standard such as XML, and features sendreceive acknowledgement. Can act as a bearer channel for file transfers and executable commands. n
n
n
n
n
n
n
Is asynchronous, featuring store and forward capability and in-order message queuing, and fostering a deferred interaction/indeterminate duration metaphor for applications[4]. Offers a service architecture through both client connections and pluggable components (where pluggable components can be close-to-the-metal C or a lightweight language such as Python). Offers a service architecture that is Network Address Translation (NAT)–friendly and can carry XML-RPC and SOAP style messages. Enables the easy creation of user interfaces through an instant messaging interface or through custom clients, again with “platform plasticity”: It is O/S-agnostic and multiple implementation language–friendly.
Tip In this book, the examples are shown mostly in Java and Python, both because those are the languages we are most familiar with, and because they are two of the most robust and mature libraries available for Jabber. If you email us with questions about the examples or other relevant topics, we will be most able to help you in those languages but, as we mentioned earlier, dedicated people have written libraries for other languages (including Perl, Macromedia Flash, C#, PHP, Visual Basic, and C++).
With such an architecture and supporting libraries you could create applications that act on the user’s behalf to accomplish goals on a deferred or delegated basis, or alert the user if he is online when something of interest happens (or wait until the user became available and inform him then). Applications could become more conversational, could present a friendlier veneer. An architecture could help us as developers to create applications that are not trapped by current firewall restrictions of typical HTTP-based client/server architectures, that could allow, for example, an application acting on a user’s behalf to reach back to that user on a client trapped behind a firewall. 3 Suchman, Lucy (1987). Plans and Situated Actions:The Problem of Human-Machine Communication. Cambridge: Cambridge University Press. 4 Deferred interaction and long duration are two of the defining hallmarks for agent-based applications.We will be showing you how to build applications in which an agent operates tirelessly on the user’s behalf until instructed to terminate.
Shortcomings of Traditional Tools
Now for the surprise of the day: Jabber can help you achieve all these application paradigms. Let’s now drill down into Jabber Technology and talk about how you can do these things.
Jabber Is… Jabber enables you to provide built-in or client-based services based on an open, asynchronous, extensible, decentralized, and secure XML protocol riding directly on TCP/IP to provide real-time exchange of messages and presence information between two endpoints on the open Internet or between the open Internet and a corporate intranet.Whew! Quite a mouthful, wasn’t it? Let us pick that statement to bits and explain a bit more fully.
…Built-in Services… A Jabber server is the combination of a message switch and service backplane, which hosts slots for plugin components.There are three ways to fill a plugin slot in the Jabber backplane and a fourth way to offer a service that looks like an ordinary client, explored in the next section and shown in Figure 1.1. A server is responsible for providing the following minimal set of services: Handling client connections and communicating directly with Jabber clients Communicating with other Jabber servers to give clients homed on other servers seamless access to clients homed on this server Acting as a “plugin manager” to manage and pass messages between components loaded with the server n
n
n
The Jabber server provides these services and will be the basis on which we build the applications in the book. Library Modules First, you can create a component by creating a library module and then linking it directly into a Jabber server.When the server is started/restarted, the service becomes available to clients and there is a protocol for presenting loaded services to clients.These are shown in Figure 1 as “lib modules.”They are intimately entwined with the server and incur the least run-time overhead of the service connection methods—at the expense of requiring development in a C-language environment. TCP/IP Sockets For those of us who have sworn off programming in C except in dire emergen cies, it is also possible to install a service across a TCP/IP socket connection and speak back and forth to the Jabber server that way. If you want to write in Java or Python or Perl, you’re free to do it that way. As long as you write the correct XML messages back and forth, the server considers the component “plugged in.” A nice advantage here is that you can
11
12
Chapter 1
What Is Jabber Technology?
load level your components across a set of servers to create a naturally scalable set of services.
Figure 1.1 Jabber Architecture.
These socket-based services can be either the initiator of the connection (that is, the server waits for them to connect) or the target of the connection (that is, the server attempts to connect to them when it starts).This provides additional flexibility in that the server and its component services don’t necessarily have to be started all together. Tip To route streams of messages accurately between the various types of componentry, the server and connected components embed something called an XML namespace into the message. For example, the initialization sequence coming from a component that is connecting via a TCP socket will have a phrase string in the message that says jabber:component:accept. The startup of an ordinary client will contain the phrase jabber:client. In this way, the server can do bookkeeping to sort out where in its internal tables to begin looking for the appropriate handler libraries.
STDIO In case you run across older server implementations, we should mention that older implementations allowed you to operate via STDIO, starting the service plugin from an
Jabber Is…
call when the server initialized.This was a fairly tight coupling and fraught with points of failure, and in practice was difficult to implement and manage. It worked such that after the server started the service process, the pair would then communicate by using the process’s standard input and standard output streams.This was a fragile connection path, and was subject to differences in the way different operating systems worked. If you run across implementations that use STDIO, you should reimplement them specifying jabber:component:accept in the jabber.xml configuation file. (See Chapter 5, “Extending the Jabber Server,” for server details and an implementation example.) Although older texts may refer to using STDIO, support for it has been dropped from the open source server. Further, it will not be included in future versions of the server. Our understanding is that the commercial “industrial strength” server from Jabber, Inc. will drop support for it as well. Using TCP/IP sockets is a much cleaner and more robust solution.
exec
Service Discovery Having all these services is great, but you need some way to find them. Jabber provides a mechanism for discovering services through browsing that uses XML namespaces to identify the capabilities of each service. These capabilities are advertised in the element of the jabber.xml configuration file. Each service has a unique (within this server) Jabber identifier, known as a JID. Here’s an example that we’ll be using later in the book: jabber:iq:register jabber:iq:gateway
This stanza specifies an internal name (JID) for the service (“weathercheck”) and an external name (“Weather Checker”) that gets displayed when a user asks for a display of services available for registration, as shown in Figure 1.2:
Figure 1.2 Server information.
13
14
Chapter 1
What Is Jabber Technology?
The elements let the clients and server know to what namespaces the service will respond. Chapters 4 and 5 cover building service components extensively, and Chapter 2 covers configuring the server to load components.
Server and Client Interactions At this point, it may prove instructive to see how this potpourri of components all fits together. Look, for example, at how personal presence issues are handled between a client and the server. Suppose a client, dana, wants to change his presence information to Do Not Disturb. He changes his presences in the client GUI, and a message is sent to the server. The client constructs and sends an XML fragment (called a “packet” in Jabber-speak) like this to the server: dnd0
The server recognizes the tag and chooses the presence module (part of the Jabber Session Manager component) to handle the packet, including telling other clients. If you start up the Jabber server in debug mode (see Chapter 2 for details), you can see the packet from the client being handled: First, the server’s managed I/O (MIO) input handler receives the packet from an open socket between it and the client: mio.c:760 MIO read from socket 16: dnd0
The server sends the presence indication back to this client first and then everyone on this client’s roster. First, it delivers to the client making the presence change (lines 2-7): 1: mod_presence new presence from dana@localhost/Exodus of ➥ ➥dnd0 2: deliver.c:257 deliver(to[dana@localhost], ➥from[dana@localhost/Exodus],type[2],packet[ ➥dnd0]) 3: users.c:143 js_user(dana@localhost,A0AD7B8) 4: deliver.c:55 delivering locally to dana@localhost 5: modules.c:135 mapi_call 3 6: modules.c:158 MAPI A05CEF8 7: mod_presence deliver phase
Next, it delivers to others on the client’s roster. Here, the server attempts to deliver to client:
bill, another
8: deliver.c:257 deliver(to[bill@localhost],from[dana@localhost/Exodus],type[2], ➥packet[ ➥dnd0])
Jabber Is…
9: users.c:143 js_user(bill@localhost,A0AD7B8) 10: deliver.c:55 delivering locally to bill@localhost 11: mod_presence deliver phase
The client, bill, is offline, but because the jabberd doesn’t maintain the state for individual rosters, it doesn’t know this and the attempt will fail, but it’s a graceful fail from the server’s standpoint: It has made a best effort attempt to deliver the message on the following line 13.The packet just gets dropped (line 14). Other packets have different policies for how to handle offline users—if this had been a message rather than a presence packet, it would have been stored and delivered to bill@localhost when he next logged in. 12: sessions.c:301 THREAD:SESSION:TO received data from dana@localhost/Exodus! 13: offline.c:54 THREAD:OFFLINE received bill@localhost’s packet: dnd0 14: util.c:75 dropping 503 packet dnd0
When bill comes back online, as shown in Figure 1.3, his client wants to inform other clients of his return to the land of the living. His client gets its own roster stored by the server in an XML file (line 1) and the core module deliver.c routes the packet containing the roster names (line 3). 1: xdb_file.c:109 loading ./spool/localhost/bill.xml 2: deliver.c:474 DELIVER 1:sessions JabberBook JabberBook JabberBook 3: deliver.c:678 delivering to instance ‘sessions’
The presence delivery mechanism formulates presence messages for each name in the roster, and determines their presences via their responses or lack thereof. If the packet is not routable, then the assumption is that the roster member is offline, and bill is updated with the presence responses from his roster members, or an offline indication if they never respond to his attempt to update their knowledge of his presence. In Figure 1.3,
15
16
Chapter 1
What Is Jabber Technology?
the client dana has its debug window open and you can see being received.
Exodus - bill@loc ... Exodus Tools ++
-
bill’s
presence message
X
Help
+ +
JabberBook
Jabberd
caitlin dana jane
Available
Exodus - jane@lo ... Exodus Tools ++
-
Jabber Instant Me...
X
Help
File
Edit
View
Tools
X
-
+
+ + Add
Friends
Find
Status
++
X
-
Help
+ +
Rooms
JabberBook
bill
Exodus - dana@I ... Exodus Tools
Help
JabberBook
bill
bill
dana
caitlin
jabber Powered caitlin@localhost [Available]
Available
Available
Debug
-
Current JID: dana@localhost/Exodus RECV: available 0 RECV: available 0
Figure 1.3 Handling presence.
X
Jabber Is…
Presenting this little example here—somewhat out of context, as it were—provides a place to talk about how one should view writing components. First, it’s important to acknowledge that certain services may be accomplished best as a part of the core Jabber server—both for speed and efficiency (rosters can get big and the potential for traffic spikes is always there) and because the Jabber server is much like an internet router or switch: Operations that involve switch-like or hub-like services should be a part of the switch. Sometimes you may have to bite the bullet, hunker down, and write C code and bundle it directly into the Jabber server. In the vast majority of cases, however, you are going to want the flexibility to distribute services onto their own CPU (like our national weather service).To achieve service manageability, for example, you might want to fix a bug in a service without taking the entire Jabber daemon down.Terminating the weather service with a control-C simply closes the socket connection to the jabber server.The listen() posted by the server is still intact.You can fix your bug and restart the distributed component and connect back up to the jabber server[5]. Of course, components are not the only way for Jabber to provide services, which provides a nice segue to the next topic.
…Client-based services… The usual, mundane experience of instant messaging for most people involves operating through a client interface with a roster full of other humans. But think about it for a moment: Nothing requires a member of a user’s roster to be a human, right? As long as a Jabber client responds to well-formed XML messages from other Jabber clients with well-formed XML messages, the client is perceived by the Jabber server as a perfectly acceptable member of a Jabber community. And that gives us a really great mechanism for delivering services to Jabber-using humans or other applications. Imagine having a client on your roster that asynchronously delivers messages whenever a change in the weather forecast occurs. Actually, you don’t have to imagine it—extending Jabber to architect extended clients is one of the topics covered in Chapter 5. Figure 1.4 shows two GUI-based clients with humans at the helm. Also shown is another client (a weather service) which for all intents is always on, and always available to other clients who add it to their rosters. It has additional logic such that when the information provided to it by an Internet source changes, it generates an ordinary instant message to all the subscribers on its roster. As shown, the service has a functional wrapper that encapsulates the capabilities of the weather service, which may be arbitrarily complex (for example, it might be talking to the Internet or a database).With the simple addition of a Jabber binder, the service can then appear as a client to other Jabber clients, and thus be added to the roster of human 5 The same thing doesn’t hold true for a component connecting through STDIO (which, as we pointed out previously, is a strongly deprecated usage and eliminated in future server implementations) or one to which the server connects when it starts.
17
18
Chapter 1
What Is Jabber Technology?
users.Thus, whenever there’s a change in the weather forecast, the service can notify human users either via presence messages or normal instant messages.The developer of the weather service doesn’t have to develop a customer UI for the service—clients experience the service as a conversation with another client.
! "" #$% "&
#
"
' (
!"
Figure 1.4 Extending the concept of client applications.
Finally, as shown in Figure 1.4, a client can also simply be a script that interacts with the Jabber server and disconnects. A couple of those are demonstrated in this chapter just to help you whet your appetite and to show how easy it is to write something useful against the Jabber messaging framework. Note that we will use a couple different terms to talk about client-based services in this book. One is client and the other is component. Generally, a client is easier to construct (you can find examples in Chapters 3 and 5, for example) when you do not have
Jabber Is…
administrative rights to the Java server, but can be less efficient (when lots of traffic is involved) than a component, which connects directly to a Jabber server. The tipping point between implementing something as a client or as a component can be delicate. Because a client connects to a server component (in an arrangement called c2s) that handles client-to-server message traffic and then connects to the server, it is not as efficient at handling large traffic volumes.Therefore, anything that affects throughput on the c2s component connecting a client to the server (such as a single user transferring a large MP3 file) affects the responsiveness experienced by every end-user routing over that c2s component. Another way to look at it is this. Suppose a human user (User A) wants to invoke a client-based service (let’s call it a Bot for ease). A message going to the Bot goes through this path: User A -> c2s component -> server -> c2s component -> Bot -> c2s component -> server -> c2s component -> User A (4 hops through c2s for the round trip). In contrast, a message going to a component takes this path: User A -> c2s -> server -> Bot -> server -> c2s -> User (2 hops round trip through the c2s component). A personal Bot that will not be heavily burdened is fine as a client, or when you don’t have admin rites to a server to set up a component. But the load should always be considered when thinking about which one to write. We cover both ways of writing client-based services, but we wanted to give you some early exposure to the idea that there can be more than one way to create services, and no one way is necessarily the “right” way. A personal Bot that keeps your to-do list and that is accessed by only you is fine as a client, but if you create a groupware picture-sharing application, then message load should weigh more heavily in your design decisions.
…Open… Whereas most of the popular IM protocols, clients, and servers—most notably AOL Instant Messenger (AIM)—are closed and proprietary and therefore difficult (though not impossible) to figure out how to interoperate with or leverage, the Jabber protocol is free, open, public, and well documented. Unlike certain other open licensing schemes, there is nothing restrictive about using Jabber.You can make derivative software from it—anything from your own servers to myriad types of clients or components, and release it under any licensing scheme you wish. In our research work, we developed a massive test automation suite using Jabber protocols as the message transport system and Jabber clients as the user interface.We certainly could not have done this with the closed and proprietary IM systems.What’s most important for you as developers to seize upon is the fact that lots of good examples of projects that use Jabber exist in a number of languages and user experience models.
…Asynchronous… A Jabber server–mediated connection guarantees delivery of messages while the destination client is in session.When a client goes offline, the Jabber server uses store and forward to deliver messages to a client when it reconnects.
19
20
Chapter 1
What Is Jabber Technology?
The utility of asynchronous guaranteed messaging and store and forward delivery is not just useful in the realm of messages between humans, by the way.We promote Jabber as a system architecture that enables systems to talk with systems. Considered as a command and control architecture, this means that even though a user client might go offline, guaranteed, in-order delivery of results is assured. It also means that even though a service that is implemented as a client might go offline, commands for the service will be delivered in order when the service returns.You might, for example, take a service offline to deliver the newest and greatest version, and when it came back online, it would get all the user input messages sent to it. Tip Of course, a service developer (that is, you) is responsible for maintaining compatibility between versions of a command-and-control–based client acting as a service. If your previous version accepted update weather forecast from your clients as input constituting a command, then your current version should do this, too.
…Extensible… Jabber is extensible in multiple senses of the word. In one sense, Jabber is extensible because you can plug in new capabilities very easily, as we will demonstrate. Additionally, you can use XML namespaces to define new message types. An XML namespace is a qualifier for XML messages that allows an unambiguous meaning to the message.The basic idea of namespaces is that the string name in one context does not have the same meaning as in another.You can design an application whose messages are special to that application and specify a namespace that uniquely defines a dialog for both participants (client and server) in the application. A typical Jabber server as shipped does much of its work with only three XML tags: , , and . Of these, most of the client-management transactions and transaction responses are carried in tags.With so few defined tags, namespaces are absolutely essential to give richness to the large number of potential dialogues between servers and clients. Notice in the earlier example of a client retrieving its roster and then notifying roster members that the XML message constituting the roster fetch used a query involving a specific namespace (xmlns=”jabber:iq:roster).This in effect notified the server that this was not just any old query, but a query about rosters. 1:
And then, when the server delivered the roster to the client, it constructed an XML message like this: 2:
Jabber Is…
Friends Friends
From reading the xmlns attribute of the list streamed to it was in fact a roster.
tag, the client knew that the
…Decentralized… Earlier, we talked about Jabber as a P2P enabler.We also said that Jabber is set up to be client-to-server-to-client.This means that all message traffic going from Alice’s computer to Bob’s computer must transit a Jabber server (or a network of peered Jabber servers). Thus, some purists would say that Jabber is not purely P2P.We maintain that there’s very little substantive difference from the user’s standpoint and positive differences from the developer’s standpoint. From the client’s standpoint, the alternate address space offered by Jabber is perceived as being totally decentralized[6]. A user can connect from any machine on the Internet and converse with a human, a software agent, or a service anywhere else on the Internet. Using a server-based implementation means that you, the developer, can stage and develop your code on something as simple as a laptop before deployment, and then move your application to a large-scale server or server farm, where user accounts can be secured, backed up, and scaled up as needed. Servers do maintain direct server-to-server connections to transfer messages from users and services hosted on one server to users and services on another.
6 It is possible for Jabber clients to connect directly from socket to socket with one another.Two end points can negotiate connections, for example to transfer files, but those “out-of-band” connections are always negotiated first through the server. In the case where one client is behind a firewall—or both of them are—this may not always be possible, in which case the server will intermediate the transfer.
21
22
Chapter 1
What Is Jabber Technology?
A Jabber client always connects to a specific Jabber server on a TCP socket over port 5222, unless you specify another port, which you might do for a private application, for example.You can start the server up on another port by modifying a tag in the Jabber server configuration file, jabber.xml.The bi-directional socket connection allows asynchronous operations.Therefore, both clients and server code have to make sure that they do any necessary cleanup.
…Secure… Even though Jabber clients reach through non-routable intranets, a Jabber server can insulate itself from the Internet and other Jabber servers by controlling which connections it will accept. In terms of message security, it is possible to use Secure Socket Layer (SSL) to create a secure client-server communication link.
…XML Protocol… Jabber uses an XML-based message set to send queries and ordinary conversation between clients. Creating a useful transport and session layer atop XML is really Jabber’s raison d’etre.The Jabber server, at its heart, is really a smart data switch that manages the connectivity of the applications that are using it as a backplane. Jabber has some other wonderful capabilities that enrich and inform applications, such as presence and availability detection and reporting, but fundamentally it’s superb middleware that uses XML to knit distributed applications, the most famous of which are applications that help people to type text messages to one another. However, the association of chat applications with Jabber is only an artifact of historical primacy.
Riding on TCP/IP Jabber uses straight sockets rather than, say, HTTP for a variety of reasons. For one thing, it’s “closer to the bare metal” than HTTP. It doesn’t require that addresses look like URLs as HTTP does. Also, it can be easier to safely tunnel through firewalls with straight TCP/IP. Finally, the dynamics of a bi-directional communications can be handled better with persistent TCP connections than with stateless HTTP connections. Persistence means that stateful knowledge of the conversation can be maintained for the full course of the interchange.Therefore, there’s no need to embed cookies or create hidden fields in constructed HTML pages.
Provides Real-time Exchange of Messages The preceding section mentioned that the innovators behind Jabber didn’t want Jabber IDs to look like URLs. And they don’t—they look like email addresses.The reason for this is pretty simple when you think about it. Architecturally, a network of Jabber servers, each serving as (in effect) an ISP for a set of clients, is just like email. Clients receive their various messages from the server to which they are directly connected. Servers speak to one another to transfer messages for the clients they host.
Jabber Is…
The big difference between email and Jabber instant messaging is that because the server can tell when a client is online, it delivers the message immediately; otherwise it will store the message for an offline client and forward it when the client connects to the server again, acting essentially like email.The potential immediacy of message delivery means that Jabber acts like a real-time messaging backplane (based on sockets) between elements of a distributed application.The store and forward capability means that messages destined for an application are guaranteed delivery. Obviously, you as the application architect have to contemplate how to handle potentially stale messages.
…And Presence Information The idea that presence and availability can be used to make connections to services less fragile is a really compelling concept. Consider some of the middleware connection strategies that have characterized distributed applications in the past, such as CORBA or RMI. Connections across these backplanes always had to have a lot of scaffolding to assure that an application was robust in the face of network failure. If you look at CORBA, RMI, or the standard transports for XML-RPC, SOAP, and other forms of XML messaging or at tunneling via HTTP, none of these mechanisms provides any information about the availability of nodes in the network. Jabber addresses the need for a set of open protocols that enable the exchange of highly structured information in an asynchronous, real-enough-time manner between network endpoints. Asynchronous here means that any participant in the conversation can say anything (okay, not anything, but anything within the definition of the namespace) at any time and other participants are expected to handle the conversation. When a service publishes a presence message like this: Online
The meaning is pretty unambiguous. Using Jabber as a backplane, the sides of the application “know” that they can converse, and it’s a given that even if there are transient network failures, when connections are re-established the pent-up message stream will be delivered in order.With presence and availability management, consider how easy it would be for a system administrator to assess the state of a multitude of hosts that are being controlled by having a Jabber client on each host publish out the state of the host as presence information.
…Between Two Endpoints on the Open Internet Because Jabber uses a familiar and friendly user@host notation, no special addressing schemes need to be supported. Anything that conforms to normal Internet addressing can participate, whether it’s a human-piloted chat client or a garage door opener on your home network.What’s not good about the addressing scheme is that it relies on the
23
24
Chapter 1
What Is Jabber Technology?
normal Internet architecture of DNS to locate host to route to user.This means that Jabber is subject to the restrictions of “normal” Internet addressing and does not use a private address space based, for example, on nicknames, as AOL Instant Messenger and Yahoo Pager do. On the flip side, because Jabber client addresses are so close to the basic SMTP (Simple Mail Transfer Protocol) scheme, there is no need for a centralized naming authority, so Jabber is more easily scalable.
…And a Corporate Intranet Jabber was meant to run on the open Internet, and therefore it does not “punch through” to private non-routable addresses (10.*.*.* or 192.168.*.* networks, for example) the way that some P2P applications can.This can make it more acceptable to corporate IT mavens, while allowing it to act as a secure network protocol for doing such things as network administration.The “Run your own server” model is especially appealing to corporations. In the same way that they have run their own email servers (and for pretty much the same reasons,) they can now run their own internal IM servers.
A Useful Application to Jump-Start Your Interest Finally, some code for us to play with.To illustrate some of these principles, let’s look at a couple implementations of a useful client. If you have downloaded the jabberd server software and installed it as we detail in Chapter 2, you now have a working Jabber server on localhost, your own PC. If you haven’t done that yet, go ahead and take the time to do it; otherwise you’re missing out on all the fun.We encourage you to do that to follow along with the example. Go ahead—Chapter 1 will be here when you come back. With the jabberd running, we hope you have also tried “talking to yourself ” by bringing up a pair of clients, registering new users through a client,[7] and passing messages between them. If you haven’t tried ping-ponging messages back and forth, start your jabberd (for example, in a command window on Windows) and let it run.Then start a session in a client and open the debug window to look at the stream of XML messages going back and forth. If you’ve gotten this far, you are exercising Jabber’s P2P capabilities to exchange messages and presence through nice GUI clients, probably using a jabberd server set up on localhost.That’s great, and a good place to start with all your experiments with Jabber. You’ll want to try out new things on your localhost server before unleashing them on an unsuspecting world, and just about any machine you would care to write code on is quite capable of hosting a jabberd.
7 A client we currently like for MS Windows is the nicely turned out WinJab client and its newer version, Exodus.
A Useful Application to Jump-Start Your Interest
A Practical User Creation Script (Java version) As we said previously, you don’t have to write complex or even complete applications to interact with the Jabber framework. Considering Jabber to be a protocol that can absorb, route, and create responses for well-formed XML messages, you can write something that exercises a narrow functionality within the larger space of the server.Writing “just enough” jabber is something we will show many times in this book (possibly leaving additional elaboration to the reader, as they say). Here’s a user Java account creation script in its entirety. Let’s look at it this way first and then break it down into conceptually complete subparts.To operate it from a command line, you will need to install a Java runtime or JDK (see Appendix D for a URL reference) and the JabberBeans library (see Appendix D for a URL reference). Start by entering the following code into your favorite text editor and saving it as NewUser.java.
Listing 1.1—NewUser.java 1:// example user account creation script 2:import java.net.*; 3: 4:import org.jabber.jabberbeans.*; 5:import org.jabber.jabberbeans.util.JID; 6:import org.jabber.jabberbeans.Extension.*; 7: 8:public class NewUser { 9: private String userName; 10: private String password; 11: private String jabberServerName; 12: 13: public NewUser(String u, String p, String h) { 14: this.jabberServerName = h; 15: this.userName = u; 16: this.password = p; 17: } 18: public boolean register() { 19: // fire up a connection to the host grabbed from the command line and 20: // register the username, password, and a resource name with the 21: // server. If you have succeeded, then you will find a .xml 22: // in the default directory -- usually /spool/. 23: System.err.println( 24: “user name -->” 25: + userName 26: + “ password-->” 27: + password 28: + “ server-->”
25
26
Chapter 1
What Is Jabber Technology?
Listing 1.1—Continued 29: + this.jabberServerName); 30: InfoQueryBuilder iqb; 31: IQRegisterBuilder iqRegb; 32: iqb = new InfoQueryBuilder(); 33: iqRegb = new IQRegisterBuilder(); 34: iqb.setType(“set”); 35: iqb.setToAddress(new JID(jabberServerName)); 36: iqRegb.set(“username”, userName); 37: iqRegb.set(“password”, password); 38: try { 39: iqb.addExtension(iqRegb.build()); 40: ConnectionBean cb = new ConnectionBean(); 41: cb.connect(InetAddress.getByName(jabberServerName)); 42: cb.send((InfoQuery) iqb.build()); 43: } catch (Exception e) { 44: e.printStackTrace(); 45: return false; 46: } 47: return true; 48: } 49: 50: public static void main(String[] args) { 51: if (args.length != 3) { 52: useage(); 53: System.exit(0); 54: } 55: NewUser n = new NewUser(args[0], args[1], args[2]); 56: System.out.println( 57: “Registration was “ 58: + (n.register() ? “Successful.” : “Not Successful.”)); 59: } 60: 61: public static void useage() { 62: System.err.println(“Usage: NewUser ”); 63: } 64:}
If you want to use this script yourself, then you should just edit this code into a .java file with your favorite code editor or IDE, compile it into a .class file.Then toss together a little .bat or .sh file to work from. A Newuser.bat for our Windows platform looks like: C:\java\bin\java.exe -classpath \jabberbeans.jar; NewUser %1 %2 %3
A Useful Application to Jump-Start Your Interest
You will have to adjust the locations in the classpath as appropriate.The basic idea is that you will need the JabberBeans library from wherever you installed it and the folder containing the .class file. You would expect the script invoker to use a command line like this: NewUser
So what’s happening in this script-like Java program? These lines bring in the JabberBeans, the Java Jabber library: 4:import org.jabber.jabberbeans.*; 5:import org.jabber.jabberbeans.util.JID; 6:import org.jabber.jabberbeans.Extension.*;
And here, we import the package containing InetAddress as a convenience, because we’ll be asking for the Internet address of the named Jabber Server a bit farther down in the code. 2:
import java.net.*;
You could have done this all in the main() method and avoided some of the class loading overhead, but it’s barely noticeable and you may want to use this class later as a part of a larger library. So we take the hit and make a NewUser class, then instantiate it in main(). 8:public class NewUser { 9: private String userName; 10: private String password; 11: private String jabberServerName; 12: 13: public NewUser(String u, String p, String h) { 14: this.jabberServerName = h; 15: this.userName = u; 16: this.password = p; 17: }
Okay, next is the meat of this Java-based script; the thing we really want to showcase for you. Here, we fire up a connection to the host grabbed from the command line and register the username, password, and a resource name with the server. If you have succeeded, then you will find a .xml in the default directory, usually /spool/. In public boolean register(), you first give yourself a blank slate for creating an message (InfoQueryBuilder iqb), and then fill it up with registration-specific elements (IQRegisterBuilder iqRegb).The InfoQuery built by the InfoQueryBuilder acts as an overall container for the registration message built by the IQRegisterBuilder. A generic InfoQuery looks like this:
27
28
Chapter 1
What Is Jabber Technology?
information custom to the namespace..
If you invoke the script like this on Windows: NewUser “alpha” “budgie” “localhost”
The packet that you should build in this specific case looks like this: alpha budgie
Notice that you are giving the a by using the namespace xmlns= and you are filling in information that is relevant to that namespace.The Jabber server uses the namespace to decode what you are trying to say to it. An InfoQueryBuilder is an absolutely essential abstraction because the number of possible fields in an InfoQuery object is far too great. Constructing an InfoQuery message from scratch is something you wouldn’t wish on the worst developer in your cubicle farm. ”jabber:iq:register”
Tip In this case, we are building an InfoQuery, but there is also a composite class for messages (MessageBuilder) and presence messages (PresenceBuilder).
At any rate, you first create instances of the builder classes, which are initialized with empty values. 18: public boolean register() { 30: InfoQueryBuilder iqb; 31: IQRegisterBuilder iqRegb; 32: iqb = new InfoQueryBuilder(); 33: iqRegb = new IQRegisterBuilder();
Next, you give the query a well-formed type, and specify a recipient (the Jabber server acting as the ISP). Notice that there is a general set() method to set an arbitrary name-value pair. In this case you want to provide a possible user ID and password pair. You could have set a few more parameters, of course. One way to find out what a particular Jabber server instance would like to see would be to ask the server first by sending it an InfoQuery packet.We’ll show you a technique for doing that in the Python example a little further along in this section.
A Useful Application to Jump-Start Your Interest
You set the query type and the recipient for the outer container of the message as follows: 34: 35:
iqb.setType(“set”); iqb.setToAddress(new JID(jabberServerName));
Next, you set the two relevant name-value pairs of the message’s query-specific content (line 36-37); then you add that content to the query’s message container (line 39). 36: 37: 38: 39:
iqRegb.set(“username”, userName); iqRegb.set(“password”, password); try { iqb.addExtension(iqRegb.build());
Finally, you create a ConnectionBean to deliver the packet to the server, connect the bean up to the server socket (line 40), and send the packet (line 42). 40: 41: 42:
ConnectionBean cb = new ConnectionBean(); cb.connect(InetAddress.getByName(jabberServerName)); cb.send((InfoQuery) iqb.build());
The server will return a packet basically acknowledging the query, but in our hubris, we’ll assume that the attempt is successful.To make this example a little more bulletproof, we should add listeners to catch and examine the return from the server, but we’ll leave that to you. If everything worked as we assumed, the server will have created a file called alpha.xml, which serves as part of the server’s database.The file should closely resemble the following: 1: 2: Registered 3: ➥budgie 4: 5: alpha 6: budgie 7: registered 8: 9: 10: 11: Welcome! 12: Welcome to the Jabber server at localhost – we hope you enjoy this service! For information about ➥how to use Jabber, visit the Jabber User’s Guide at http://docs.jabber.org/ 13: Offline Storage
29
30
Chapter 1
What Is Jabber Technology?
14: 15: 16:
Notice that in this example we implemented just enough code to get a simple job done. No more, no less. Jabber enables you to make small talk (short and purposeful conversations) or complete dialogues.We will follow this principle every time in our examples. Note Our server is configured to send all new users a welcome message when they are first created. Because the script disconnects right after making the new user, the welcome message is stored for delivery the next (actually, first) time the new user logs in. The stored welcome message is lines 10 through 14.
A Practical User Creation Script (Python version) Here’s the same example in Python.To use Jabber from Python, you need to download and install Python 2.x and the JabberPy libraries (see Appendix D). An easy way to install JabberPy (after installing Python if you haven’t already done so) is to use setup.py bundled with JabberPy. Untar the downloadable file, and as root on Linux, or an administrative user on Windows, open a command window, cd to the directory containing setyp.py, and from the command line run the following: python setup.py install
Alternatively, just copy xmlstream.py and jabber.py to somewhere in your PYTHONPATH. For example, copy them into / site-packages/JabberPy. After you’ve done this one-time operation, you should be ready to script Jabber from Python. To use the script from the command line, use python newUser.py localhost alpha budgie Work
Import the utility libraries and the Jabber Python libraries. Listing 1.2—newUser.py 1:#!/usr/bin/env python2 2:import socket 3:from select import select 4:from string import split,strip,join 5:import sys,os 6:from time import sleep 7:import jabber 8:
A Useful Application to Jump-Start Your Interest
Create a class that can be called from the command line (lines 81-88), or serve as a library. 9:class NewUser: 10: 11: True = 1 12: False = 0 13: 14: def __init__(self): 15: sys.path.insert(1, os.path.join(sys.path[0], ‘..’)) 16: if len(sys.argv) == 1: 17: self.usage() 18: sys.exit(o) 19:
In registerNewUser, you need to construct a connection instance using the Client class on line 26 and then connect to the server. It’s a matter of style to do it this way. Alternatively, you could have set parameters and then connected as we did in the Java example. 20: def registerNewUser(self): 21: print “Register a New User” 22: jabberServer = sys.argv[1] 23: userName = ‘’ 24: Password = ‘’ 25: Resource = ‘default’ 26: con = jabber.Client(host=jabberServer, debug=True, log=sys.stderr) 27: try: 28: con.connect() 29: except IOError, e: 30: print “Couldn’t connect: %s” % e 31: sys.exit(0) 32: else: 33: print “Connected” 34:
Now (lines 35-38) you can set event handlers for various messages you might expect from the server: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:
con.setMessageHandler(self.messageCB) con.setPresenceHandler(self.presenceCB) con.setIqHandler(self.iqCB) con.setDisconnectHandler(self.disconnectedCB) # Set up the jabber account con.setRegInfo(‘username’, sys.argv[2]) con.setRegInfo(‘password’, sys.argv[3]) con.setRegInfo(‘resource’, sys.argv[4]) con.sendRegInfo()
31
32
Chapter 1
What Is Jabber Technology?
You are essentially registered now and can disconnect and exit. 46: con.disconnect() 47: sys.exit(0) 48:#
Define a simple usage generator: 49: def usage(self): 50: print “%s is a python jabber client to register a new user” % sys.argv[0] 51: print “usage:” 52: 53: print “%s ” % sys.argv[0] 54: print “ - connect to server and login “ 55: sys.exit(0)
Here are the various callbacks required by the event model in JabberPy.They are essentially empty, but needed for completeness. 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69:
def messageCB(self, con, msg): “””Called when a message is recieved””” print “INFO: messageCB” def presenceCB(self, con, prs): “””Called when a presence is recieved””” print “INFO: presenceCB” def iqCB(self, con,iq): print “INFO: recieved InfoQuery” print “Iq:”, str(iq) def disconnectedCB(self, con): print “ERROR: network disconect” sys.exit(1)
Finally, the calling method: 70:if __name__ == ‘__main__’: 71: n = NewUser() 72: if len(sys.argv) == 5: 73: n.registerNewUser() 74: sys.exit(0) 75: else: 76: n.usage() 77: sys.exit(0)
A Useful Application to Jump-Start Your Interest
Jabber’s Open Source Development Heritage (And Its Implications) There are many exemplary usages of Jabber: commercial grade servers from Jabber, Inc. (www.jabber.com) that can and do host many thousands of simultaneous users, free servers from the Jabber Software Foundation (jabber.org), some commercial and many (!) free clients (again see the listings at jabber.org). When you use Jabber in whatever capacity—as a message transport system, intermixing ready-made clients with your own services—nothing in the licensing agreement prevents you.You should as a matter of honesty and developer courtesy, give credit where it’s due, but in general you are free to use Jabber, and leverage the community in pursuit of your own dreams and schemes. Be aware, however, that some third-party packages and modules are deployed by their authors under more or less restrictive licenses, such that if you want to use them commercially, you might not be able to use them at all or you might have to make separate arrangements with those authors. Our philosophy persuades us to give back in greater measure than we receive, but your mileage may vary. Certainly, after amazing your friends and employers with some of the nifty things possible with Jabber, you may well want to become more deeply involved with the Jabber community, and that’s our hope too. In any event, you may choose to write code for profit that you base on Jabber code based on Jabber services and messaging.The Jabber community does not preclude that possibility for those who might want it. Jabber’s license, although a recognized open source license, accommodates that.
Conclusion (or Rather, The Beginning) We hope that after reading this chapter you cannot wait to begin digging into the rest of the book. At this point you should have come away with the idea that this “Developer’s Handbook” is oriented toward providing you with insightful examples and a good fundamental understanding of the available APIs. Finally, you should expect a lot of exploration of the nooks and crannies of Jabber and the philosophy of P2P solutions in general. As a result you should also anticipate sample source code that actually works. We hope—and endeavor—to exceed your expectations.
33
2 Installing and Configuring Jabber Software
Farther along we’ll know all about it / Farther along we’ll understand why / Cheer up my brother, live in the sunshine / We’ll understand it all by and by. Traditional gospel song
N
OW THAT CHAPTER 1 HAS SET the stage by discussing clients and servers and how they operate in the overall Jabber architecture, the next three chapters explore the server and the client in greater detail.This chapter describes how to set up Jabber servers and other software so that you can try the examples in this book.
Downloading the Server Software The first thing you’ll need to do is download the jabberd server software.There are several Jabber servers, including commercial ones, but we’ll focus here on the open source reference implementation managed by the community at jabber.org. First, download the installation package appropriate for your operating system. For Linux and other Unixes, the software is available at http://jabberd.jabberstudio.org/downloads. As of this writing, the current version of jabberd is 1.4.2, so the installation file is called jabber-1.4.2.tar.gz. It contains the source code and build files required for the Jabber server. Download this file to your Unix machine. You can use the same jabberd source code package under Microsoft Windows just the same as under Unix if you have the Cygwin Unix tools installed.They are available at http://www.cygwin.com. If you don’t want to compile the server yourself, you can install the pre-built server. A pre-built installation package for Microsoft Windows is
36
Chapter 2
Installing and Configuring Jabber Software
available at http://jabberd.jabberstudio.org as the file called JabberD-1.4.2.exe. If you’re working under Windows, you may find it a more convenient way to get started than building the source.
Installing the Server Software Now that you have the software, let’s look at how to get it up and running. Here the Unix and Windows differ quite a bit, so they’re covered separately. Feel free to skip the part that doesn’t apply to you.
Linux and Unix The download package for Unix contains source code that you’ll need to compile for your operating system and server hardware. Fortunately, this is easy because the distribution contains the scripts necessary to build on most common platforms including Linux, Solaris, FreeBSD, NetBSD, MacOS (Darwin), and AIX.The examples in this section are from a RedHat Linux installation, but other Unix variants should be similar.The jabberd server is written in C, so you need to have the gcc compiler and the GNU make tools installed for this to work. Unpack the gzipped source code archive file by entering the following: # gunzip –c jabber-1.4.2.tar.gz | tar xf –
There’s no separate installation step for the jabberd server—you run it right out of its build directory. So decide where you want the software to run from and unpack the sources there.The directory path in which you compile jabberd is actually compiled into the server and used to search for configuration files, so although it’s not too onerous, it’s easier to not try to move the server to a different directory after it’s compiled. Unpacking the gzipped tar file creates a directory called jabber-1.4.2. Change directory (cd) into that directory and type: # ./configure
You should see some output that looks like this: # ./configure Running Jabber Configure ======================== Getting pth settings..../configure: line 1: pth-config: command not found ./configure: line 1: pth-config: command not found ./configure: line 1: pth-config: command not found ./configure: line 1: pth-config: command not found Configuring GNU Pth (Portable Threads), Version 1.4.0 (24-Mar-2001) Copyright (c) 1999-2001 Ralf S. Engelschall loading cache ./config.cache Platform: i686-redhat-linux-gnu2.4glibc2.3 Build Tools:
Installing the Server Software
checking for gcc.. (cached) gcc checking whether the C compiler (gcc ) works.. yes checking whether the C compiler (gcc ) is a cross-compiler.. no checking whether we are using GNU C.. (cached) yes checking whether gcc accepts -g.. (cached) yes checking how to run the C preprocessor.. (cached) gcc -E checking whether make sets ${MAKE}.. (cached) yes checking for compiler option -pipe.. (cached) yes checking for compilation debug mode.. disabled checking for compilation profile mode.. disabled checking for compilation optimization mode.. disabled … Setting Build Parameters.. Done. Generating Settings Script.. Done. You may now type ‘make’ to build your new Jabber system. #
The configure command checks various system settings and capabilities and creates a makefile that can be used to build jabberd. After configure finishes, type this command to build jabberd: # make
This compiles all the server code and links it into an executable called jabberd in the directory.The output of this command should look something like this:
./jabberd
# make Making all in pthsock make[1]: Entering directory ‘/usr/local/jabber-1.4.2/pthsock’ gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 jabberd/ -c -o client.o client.c gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 jabberd/ -shared -o pthsock_client.so client.o -ldl -lresolv make[1]: Leaving directory ‘/usr/local/jabber-1.4.2/pthsock’ Making all in xdb_file make[1]: Entering directory ‘/usr/local/jabber-1.4.2/xdb_file’ gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 jabberd -c -o xdb_file.o xdb_file.c gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 jabberd -shared -o xdb_file.so xdb_file.o -ldl -lresolv make[1]: Leaving directory ‘/usr/local/jabber-1.4.2/xdb_file’ Making all in dnsrv make[1]: Entering directory ‘/usr/local/jabber-1.4.2/dnsrv’ gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 jabberd/ -c -o dnsrv.o dnsrv.c
-fPIC -I../
-fPIC -I../
-fPIC -I../
-fPIC -I../
-fPIC -I../
37
38
Chapter 2
Installing and Configuring Jabber Software
gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 -fPIC -I../ jabberd/ -c -o srv_resolv.o srv_resolv.c gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 -fPIC -I../ jabberd/ -shared -o dnsrv.so dnsrv.o srv_resolv.o make[1]: Leaving directory ‘/usr/local/jabber-1.4.2/dnsrv’ Making all in jsm make[1]: Entering directory ‘/usr/local/jabber-1.4.2/jsm’ Making all in modules make[2]: Entering directory ‘/usr/local/jabber-1.4.2/jsm/modules’ gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 -fPIC I../../jabberd/ -c -o mod_admin.o mod_admin.c … gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 -fPIC -DHOME=”\”/usr/local/jabber-1.4.2\”” -DCONFIGXML=”\”jabber.xml\”” -c -o static.o static.c gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 -fPIC -DHOME=”\”/usr/local/jabber-1.4.2\”” -DCONFIGXML=”\”jabber.xml\”” -c -o log.o log.c gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 -fPIC -DHOME=”\”/usr/local/jabber-1.4.2\”” -DCONFIGXML=”\”jabber.xml\”” -o jabberd config.o mio.o mio_raw.o mio_xml.o mio_ssl.o deliver.o heartbeat.o jabberd.o load.o xdb.o mtq.o static.o log.o lib/expat.o lib/genhash.o lib/hashtable.o lib/jid.o lib/jpacket.o lib/jutil.o lib/ karma.o lib/pool.o lib/pproxy.o lib/rate.o lib/sha.o lib/snprintf.o lib/ socket.o lib/str.o lib/xmlnode.o lib/xmlparse.o lib/xmlrole.o lib/ xmltok.o lib/xstream.o lib/xhash.o base/base_connect.o base/ base_dynamic.o base/base_exec.o base/base_stdout.o base/ base_accept.o base/base_file.o base/base_format.o base/ base_stderr.o base/base_to.o -Wl,--export-dynamic -ldl -lresolv /usr/local/jabber-1.4.2/jabberd/pth-1.4.0/pth_*.o make[2]: Leaving directory ‘/usr/local/jabber-1.4.2/jabberd’ make[1]: Leaving directory ‘/usr/local/jabber-1.4.2/jabberd’ make[1]: Entering directory ‘/usr/local/jabber-1.4.2’ make[1]: Nothing to be done for ‘all-local’. make[1]: Leaving directory ‘/usr/local/jabber-1.4.2’ #
Note During the compilation, you may see a message that says: Now please type ‘make test’ to run a quick test suite. Hope it works. The distribution includes the GNU Portable Threads library, which includes a test suite. The make test is referring to a test of the threads library, not jabberd, so there is no actual need to type make test.
Installing the Server Software
As we mentioned, there is no installation or make install step. If jabberd compiles without errors, you can just run it by typing ./jabberd/jabberd, but you’ll want to edit the jabber.xml file first.The following sections cover this.
Windows If you’re going to build the source code under Windows, just start a Cygwin bash shell and follow the Unix instructions already explained.To use the pre-built installer, just run it and select the options you want. As shown in Figure 2.1, you have to select JabberD to install the server itself. Select Start Menu Shortcuts to install jabberd entries in the Windows Start menu. Select Startup Menu Entry to have jabberd start every time you log in to Windows.
Figure 2.1 Selecting the installation options under windows.
Click Next and select an installation directory as shown in Figure 2.2. Click Install and that’s it. Before you run the server, though, you should edit the jabber.xml file.That’s next.
Figure 2.2 Selecting the installation directory under Windows.
39
40
Chapter 2
Installing and Configuring Jabber Software
Initial Server Configuration The behavior of the Jabber server is controlled by the jabber.xml configuration file and command-line arguments.There are many options for each, but most of the defaults work fine for getting started.
Jabberd Command-line Arguments Table 2.1 summarizes the command-line arguments that control Jabberd. Table 2.1 Jabberd Command-line Options Option
Description
-c
Specifies the location of the jabber.xml configuration file. Specifies a number of config files to be parsed. Enables debug output and keeps jabberd as the foreground process. Location where jabberd will look for jabber.xml.This defaults to the directory in which you compiled jabberd. Causes the jabberd process to detach from the terminal and run in the background.This is useful when starting jabberd from a system startup script. Debug zones.This is useful if you want to see debug messages from just certain parts of the server. Server version. Attempts to run jabberd as the specified user.
-i -D -H -B
-Z -v (or V) -U
The debug “zones” are just basenames of the C source code filenames, such as mio.c and log.c, that were compiled into jabberd. So if you want to see debugging messages from just the mio.c and log.c files, you’d start jabberd like this: # ./jabberd/jabberd –D –Z mio,log
Tip You have to provide the –D option to get any debug logging at all turned on, so using –Z without –D does not cause any debug output to be generated.
The jabber.xml File The jabber.xml file is used to configure which services jabberd will provide and how the server relates to clients and other Jabber servers.The default jabber.xml contains several sections, which are examined in detail in the remainder of this chapter. The jabber.xml file is an XML document (see Appendix B for an introduction to XML) and at its top level, each element delimited by the tag directs the server to load a component and provides configuration information for that component. All
Initial Server Configuration
the services needed for simple instant messaging are included in the default configuration. Table 2.2 The Default Jabberd Services Default Jabber Services
Internal Service Name
Jabber session manager (JSM) Client-to-server connections Server-to-server connections Host name resolution (DNS) services Database services Transaction logging Error logging
sessions c2s s2s dnsrv xdb rlogger elogger
The jabber.xml file that comes with the distribution is set up to provide basic functionality for a single isolated server. It assumes that your server hostname is localhost and starts the c2s and s2s services on their default TCP/IP ports. Although this enables clients to connect to the server if they’re running on the same computer as the server, there are some basic configuration options that you should probably set before firing up the server. The Jabber session manager (JSM) service controls basic server information and client registration information. For example, this line (near the top, in the sessions service section) defines the name of the Jabber server: my-hostname
Note the special tag . Using this tag enables you to override the setting with a command-line option as specified by the flag attribute (“h” in this example).With the element configured this way, you could now run jabberd like this: jabber.xml
# jabberd –h other-hostname
to override the value my-hostname in jabber.xml with other-hostname. Set this line’s value to match the hostname of the computer that you’ll be using to run the server. Tip You can specify multiple entries to set up your server to run multiple virtual Jabber servers. So if you configure your server with entries like this: jabber.virtucorp.com jabber.mydomain.org There will be one Jabber server that answers to the name jabber.virtucorp.com and another that answers to the name jabber.mydomain.org, both running within the single jabberd instance. See the c2s section later in this chapter to see how to enable a single Jabber server to respond to multiple names.
41
42
Chapter 2
Installing and Configuring Jabber Software
A little farther down in the file is this line: localhost
It tells the JSM to ask the server at update.jabber.org whether a new version of the software is available when jabberd starts.There’s no reason to leave this enabled and if your server is behind a firewall or disconnected from the Internet; it won’t work anyway. You can disable it by deleting the line or commenting it out like this:
If you want to use the update feature, change localhost to the hostname of your server computer. The next item of interest in the jabber.xml file is this line:
This rather cryptic directive causes the server to try to register new users with the Jabber User Directory (discussed under “Common Optional Services” later in this chapter). Unless you install your own user directory (also below) this will tell the server to try to send vCard information to the user registry at users.jabber.org. Probably not what you want.You can delete this line or comment it out like this:
The section of the sessions (JSM) service comes next. It defines the set of services that clients can browse. Like the earlier examples, if you’re isolated from the Internet, you don’t need the Jabber User Directory section, so you can comment it out like this: jabber:iq:search jabber:iq:register -->
The next entry that you might need to change to get started is in the client connection service (c2s).The default TCP/IP port for clients to connect to the server is 5222. Some IM clients don’t use any port except 5222, so change this only if you have to.The configuration line looks like this:
This tells the server to listen for client connections on port 5222. The last piece of basic configuration controls the port on which the server listens for messages from other servers. Although you probably won’t be getting any messages from other servers right away, it’s good to know that the default port is 5269 and it’s specified in the s2s service configuration section like this:
Initial Server Configuration
Starting jabberd Now that the jabber.xml file is set, you can start the Jabber server by cding to the directory where the server code was unpacked and compiled and typing: # ./jabberd/jabberd
The server reads the jabber.xml file, loads and configures the services, and is ready to handle connections. Actually, because the path to the installation directory (the Jabber “home” directory) was compiled into the server or set explicitly with the –H commandline option, you can run jabberd from anywhere and it will find jabber.xml in its home directory. You can stop the server by typing Ctrl+C in the window in which you started jabberd. If you had it detach from the terminal (run in the background) with the –B command-line option, you can use the file in the Jabber home directory that contains the process ID for the server (called jabber.pid by default), but you can change it in jabber.xml) and use a command like this to kill it: # kill `cat jabber.pid`
If something goes wrong and your server doesn’t start up, it’s probably either a syntax error in the jabber.xml file, a leftover PID file, or a problem binding to the c2s and/or s2s TCP/IP ports. If you have an error in the jabber.xml file, you’re likely to see a message like this: # ./jabberd/jabberd Configuration parsing using jabber.xml failed: mismatched tag at ➥line 650 and column 2 #
Sometimes the error message can be a little misleading. It may say that the problem is on line 650, but the error was really somewhere before there.This happens a lot if you open a tag (say ) but forget to close it (with a tag).The parser can continue quite a way down the file before it realizes that there’s a problem. A good XML editor can help you spot and correct the problem. Normally, the jabberd deletes the file containing its process ID when it exits. But if the server exits abnormally (crashes), it can leave the PID file around and it will refuse to start if a pidfile already exists.We suppose this is to keep you from accidentally running two instances of the server. If this happens, you’ll see a message like this: # jabberd/jabberd A pidfile already exists at the specified location. Check to ensure another ➥copy of the server is not running, or remove the existing file.
Pretty self-explanatory. If another jabberd is running, kill it. If not, just delete the pidfile (jabber.pid) and try again. The other common problem is when the server fails to open the TCP/IP ports for listening.This can happen if some other program (most likely another jabberd) already has them open. If you see messages like these when the server starts, you have this problem:
43
44
Chapter 2
Installing and Configuring Jabber Software
20021102T20:36:37: [alert] (-internal): io_select unable to listen on ➥5222 [(null)] 20021102T20:36:37: [alert] (-internal): io_select unable to listen on ➥5269 [(null)]
You can use the netstat command to verify whether another process is actually listening on those ports. If not, try listening on a specific address and port instead of the default address (which the default jabber.xml uses). Change the c2s and s2s configurations so that rather than:
you use something like: 192.168.1.1
Substitute your real IP address for 192.168.1.1, of course. Caution If you use 127.0.0.1 in the preceding example, you’ll be able to connect to the server only from the same computer it’s running on.
If you’re just setting up your Jabber server for the first time, that’s all you probably need to know to get it up and running. If you’d like to dive into the configuration details a little, read on.
Service Configuration Details Now let’s look at some of the details of how the default services are configured. As was mentioned earlier, each service is defined by a section of the jabber.xml file, and each section is examined here.
Service XDB The xdb (for XML database) component handles the data storage for jabberd. Its default behavior is to store data in XML files in the ./spool subdirectory under the jabberd home directory. Listing 2.1 shows the default configuration from the default jabber.xml file. Listing 2.1 XDB Configuration ./xdb_file/xdb_file.so ./spool
Service Configuration Details
Listing 2.1 Continued
This section defines an instance of the xdb service with the name (specified by the ID attribute) “xdb”.The second line, , tells the internal message router that this component will handle data handling requests from all “hosts” (including virtual hosts) in this server.The section identifies the location of the component’s implementation; in this case it’s a Unix shared library.The section contains the configuration information specific to the xdb_file component.The only configuration item listed here is the location of the XML data files (./spool is the default).This directory is relative to the directory where jabberd was built, which is not necessarily the same as the directory in which you run jabberd. Because the jabberd:cmdline tag is used, this value can be overridden on the command line like this: # jabberd –s /var/spool/jabber
This command tells the xdb_file component to store its data in /var/spool/jabber rather than in the default ./spool directory. A couple other options can be useful in a heavily-loaded server. One of these controls the length of time that the xdb component will keep data in memory (cache it) to avoid having to re-read the files.The default behavior is to keep the data in memory forever (or until the server shuts down, anyway).This makes a small server fast, because after the data is read in, the server never needs to re-read it. Of course, the server’s memory can hold only so much, so a server with many clients needs to purge its cache sometimes. The value controls how long cached items are held in memory from the time they are read in. It might be better to cache data items based on how recently they were used, but that’s not how it works. The other option that is useful in a heavily-loaded server is called . It controls the size of the internal hash table used by xdb to reference data sets. It defaults to 509, which gives good performance in most cases. Listing 2.2 provides an example of an xdb_file component configuration that uses the and parameters. It tells the component to cache files for 2 minutes (120 seconds) and to set the cache hash table size to 13. Listing 2.2 A Customized XDB Configuration ./xdb_file/xdb_file.so ./spool 120
45
46
Chapter 2
Installing and Configuring Jabber Software
Listing 2.2 Continued 13
The spool directory (./spool in Listing 2.2) will hold subdirectories for each virtual host that the server is running as well as data stored by other components that use xdb to store their data.
Service io This service controls the Managed Input/Output (MIO) capabilities of the server. Its configuration is used to set the global default connection and throughput parameters. Other components (the s2s and c2s components, in particular) can override these values.The jabber.xml file that ships with the jabberd server contains several example configurations for the io service.The only section of it that is not commented out is this:
The tag governs how may connections can be made from a particular host (IP address). In this example, each host could make five connections in 25 seconds before its connections were no longer accepted. At the end of 25 seconds, the offending host could make another five connections. The section of the io service configuration is commented out in the default jabber.xml file and deserves some explanation because you’ll see it again in other configuration sections. As with the section, the section of the io service is the default value for the other connection services and can be overridden by them. Like rate, karma is a Jabber concept for detecting and controlling misuse of the server.The idea is that if one client sends too much data, it could monopolize the server at the expense of other clients. Each client connection is assigned a karma value and when a connection’s karma gets too low, its data are not serviced for some (configurable) period of time. So the settings control the throughput on a connection, and the settings control the number of connections that can be made.The parameters that control the karma calculation are listed in Table 2.3. Table 2.3 Karma Parameters Parameter
Default
Description
heartbeat
2
init
5 10
The period of time (in seconds) between each evaluation of a connection’s karma. The initial karma value for each connection. The maximum karma value that a connection can have.
max
Service Configuration Details
Table 2.3 Continued Parameter inc
Default 1
dec
0
penalty
-5
restore
5
resetmeter 0
Description The karma value is incremented by this amount every heartbeat in which there is no penalty. The karma value is decremented by this amount every heartbeat in which there is a penalty. When the karma value decreases to zero, it is set to this (usually negative) value. When the karma value increases to zero, it is set to this (usually positive) value. If 1 (true), when the karma value increases to zero, the byte quota is reset to its maximum.
This all seems very complicated; what does it amount to? The amount of data a client can send per second without risking penalty is equal to: (100×karma)/heartbeat The karma value starts at the value you provide for the tag, so if you set init to 100 and heartbeat to 2, the throughput allowed before the MIO starts throttling the connection is 5,000 bytes/second: (100×100)/2 = 5000 As long as the data rate stays below that level, nothing strange happens.You can effectively disable the karma limiting feature by setting to 0. You can also control which hosts will be allowed to connect to your server and which ones will not be allowed by using and tags in configuring the io service. Each of these tags can include an tag section, which lists an IP address that is either allowed to connect, in the case of the tag, or not allowed, in the case of the tag.This tag can also be used to allow or deny a range of addresses, if you use the tag.The mask is a bit mask that identifies which bits of the address are valid—much like a netmask. Listing 2.3 provides some examples. Listing 2.3 Sample Allow and Deny Settings 127.0.0.0255.255.255.255 192.168.1.0255.255.255.0 192.168.1.23255.255.255.255
If you don’t list any allow or deny tags, then connections from any host will be accepted. If you include an allow tag, then only those listed hosts will be allowed to connect. If
47
48
Chapter 2
Installing and Configuring Jabber Software
any hosts are included in both an accept list and a deny list, their connections will be rejected. Finally, the tag is used to configure Secure Socket Layer encryption between the server and its clients. If you’ve compiled the server with SSL enabled (by doing ./configure --enable-ssl before the make) you can configure your server to use SSL to protect client socket connections. /path/to/cert_and_key.pem /path/to/other/cert_and_key.pem
This example configures the io service to use the key in /path/to/cert_ and_key.pem to protect connections to IP address 192.168.1.1, and the key in /path/to/other/cert_and_key.pem to protect connections to IP address 192.168.1.100. More details on SSL configuration and encryption are in Chapter 6, “Jabber Security.”
Service c2s The c2s service manages connections between clients and the jabberd server. Listing 2.4 shows the default configuration from the jabber.xml file. Listing 2.4 The Default c2s Configuration ./pthsock/pthsock_client.so 10 10 1 1 -6 10
As you saw earlier, the name (or ID) of this service will be “c2s” and its code will be loaded from the shared library ./pthsock/pthsock_client.so.The configuration options specific to the c2s service start at this line:
Service Configuration Details
The first option is , which can be used to specify how many seconds each client has from the time it connects to finish its authentication sequence.The default setting allows the client an unlimited amount of time. Partially authenticated clients could mount a denial of service attack on the server by holding network sockets open, so a public production server should probably have this value set to something reasonable (something less than 10 seconds seems safe). By default, the c2s service does not override the io service’s setting, but does override its settings.The throughput allowed on each client connection is therefore 500bps: (10×100)/2 = 500 It’s 500bps because the value of is set to 10, and the value of is unset and defaults to 2. As mentioned earlier, the section defines the TCP/IP port on which the service will listen for connections.The tag can be used in addition to the tag to direct the service to use SSL-protected socket connections. Here’s an example: 127.0.0.1 192.168.1.100
These entries tell the c2s service to listen on port 5223 for SSL loopback connections and on port 5224 for remote SSL connections. Unlike the tag, with the tag you must list the IP address or no connections will be established. The tag enables the server’s clients to refer to the server by different names. This can be useful if your server has multiple names like myserver and myserver. mydomain.com and you want clients to be able to use the names interchangeably.This section configures the server to consider all packets addressed to myserver to be addressed to myserver.mydomain.com: myserver
Service s2s The s2s service manages connections to another Jabber server. Its configuration is very similar to the c2s service.You can see the s2s configuration that comes with the jabberd distribution in Listing 2.5. Listing 2.5 The Default s2s Configuration ./dialback/dialback.so
49
50
Chapter 2
Installing and Configuring Jabber Software
Listing 2.5 Continued 50 50 4 1 -5 50
The format looks pretty familiar by now.This service’s name will be “s2s” and the code will be loaded from the shared library ./dialback/dialback.so. It’s called “dialback” because the protocol between servers requires the server that is receiving the message to contact the sending server (that is, dial back) to help detect a server that is claiming to be one host, but is really another. More details on the dialback protocol are in Chapter 6. Looking now within the section, the tag tells the server to maintain backward compatibility with older servers. Not shown in this example is the tag, which controls the size of the hash table used to store contact information about other hosts.The default value for is 997, which doesn’t mean that this server can communicate with only 997 other servers, but just that the internal hash tables will have 997 buckets. Also not shown are two timeout values that control how long socket connections are handled between servers. If two servers send lots of messages to each other, it makes sense to just keep a connection open rather than opening and closing one every time a message is sent.The value controls how many seconds a connection will be kept open after the last packet has been sent through it. It defaults to 900 seconds (15 minutes).The tag controls how many seconds this server will hang onto a packet destined for another server, if that server is unavailable. It defaults to 30 seconds. As with the c2s service, the s2s service can override the io service’s values for and . In the default example earlier, only the is overridden. Using the formula for karma calculation: (50×100)/2 = 2500 the throughput for this connection will be limited to 2500 bytes per second.The s2s component usually has a higher karma maximum than the c2s component, because several clients’ data may be flowing through it. As mentioned earlier, the section defines the TCP/IP port on which the service will listen for connections.The last unshown option is the secret attribute.The s2s component uses the secret string as input to the dialback protocol. If you don’t specify
Service Configuration Details
one, it generates a random string, which is probably better anyway.You specify secret as an attribute to the tag, not as its own element, as shown in Listing 2.6. Listing 2.6 Specifying a Secret for s2s Connections ./dialback/dialback.so … the rest as before …
When configured this way, the server uses generate a random string.
123456
in the dialback protocol rather than
Service dnsrv The dnsrv service handles requests to lookup IP addresses given host names. Its default configuration looks like Listing 2.7. Listing 2.7 Default dnserv Configuration ./dnsrv/dnsrv.so s2s s2s
This configuration tells the dnsrv component to try two different methods to resolve hostnames: Look in the Domain Name Service (DNS) for an SRV record for a service called jabber accessible over TCP and, failing that, just look up the hostname by using the operating system’s default host lookup mechanism and send it to the s2s component there. Setting up DNS SRV records is outside the scope of this book, but you can get details about it from Internet RFC 2782. Unless you’re building a particularly large and complicated server constellation, you’ll probably never have to change any of this.
Log Services Components running within the Jabber server can create log messages to help diagnose problems and keep track of how the server is used. A log message can be tagged as alert,
51
52
Chapter 2
Installing and Configuring Jabber Software
notice, record, warn, or it can have no tag. A logger can be configured to handle one or more of these types of log messages.The default jabber.xml configuration includes two loggers: the rlogger, which is configured to handle “record” log messages, and the elogger (error logger) which is configured to handle all other log messages. Listing 2.8 shows the default configuration of the rlogger. Listing 2.8 Default Configuration of the rlogger 1: 2: 3: record 4: %d %h %s 5: record.log 6:
Line 1 declares that this logger is called rlogger. Line 2 of this configuration says that rlogger will handle log entries from any host. Of course, this logger is unlikely to see log messages for other hosts. Line 3 says that this logger handles only log entries tagged record—it ignores all others and lets other loggers handle them. Line 4 defines the message’s format. Four parts of the log record are available to be included in the message: %d—The time that the message was created %h—The name of the host that created the message %t—The log message tag (record, for example) %s—The text of the log message n
n
n
n
Because the message tag is always record, the rlogger example prints just the date, host, and message for each log record. It prints entries that look like this: 20021102T20:50:35 jim@my-jabber login ok 192.168.1.2 jim
It’s nice to be able to change the format of the log messages to make it easy for scripts to parse the log file. Finally, line 6 says that the logger output should go in the file record.log. The elogger is configured in the same manner as the rlogger. Its default configuration looks like Listing 2.9. Listing 2.9 Default Configuration of the elogger 1: 2: 3: 4: %d: [%t] (%h): %s 5: error.log 6: 7:
Service Configuration Details
This configuration looks a lot like the rlogger, with a couple of differences. First, the tag is empty.This means that this logger handles all records that are not handled by some other logger. So, in this case, it handles all records except those tagged record.Those are handled by the rlogger. The format of the output records is a little different, too (line 4). Here it also outputs record type [%t] and some formatting characters to make it more readable.The output file is called error.log (line 5) and it also outputs these records to the standard error stream (line 6). An output line from this logger looks something like this: 20021102T20:48:52: [warn] (io_select): 192.168.1.1(13) is being ➥connection rate limited
The Sessions Service Last but not least in this tour of the default jabberd components is the sessions service, also known as the Jabber Session Manager or JSM.This service is responsible for handling most of the server’s instant messaging functions. It handles the registration of new clients, message storage for clients that happen to be offline, message filtering, and so on. Compared to the other services covered so far, it’s a real monster. It’s composed of several sub-services that each perform a manageable chunk of functionality.You can see the default XML configuration for the sessions service from the jabber.xml file in Listing 2.10. Listing 2.10 Default Configuration of the JSM 1 : 2 : localhost 3 : 4 : 5 : 6 : 7 : 8 : 100 9 : 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 :
53
54
Chapter 2
Installing and Configuring Jabber Software
Listing 2.10 Continued 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
: : : : : : : : : : : : : : : : : : :
41 42 43 44 45 46 47
: : : : : : :
48 49 50 51 52 53 54 55 56
: : : : : : : : :
57 : 58 : 59 :
Jabber Server A Jabber Server! http://foo.bar/ Choose a username and password to register with this server. Welcome! Welcome to the Jabber server at localhost -- we hope you enjoy this service! For information about how to use Jabber, visit the Jabber User’s Guide at http://docs.jabber.org/ support@localhost admin@localhost Auto Reply This is a special administrative address. Your message was received and forwarded to server administrators. -->
Service Configuration Details
Listing 2.10 Continued 60 61 62 63 64 65 66
: : : : : : :
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
: : : : : : : : : : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : ./jsm/jsm.dll : : :
localhost jabber:iq:search jabber:iq:register
Let’s take this a piece at a time. Line 1 identifies this as the sessions service, and line 2 says that this service handles messages to localhost (unless you override it on the command line with the –h option). As was explained earlier, you’ll probably change this to your server’s actual name.
55
56
Chapter 2
Installing and Configuring Jabber Software
Filters The real configuration of the JSM starts with line 4 with the tag. Next, starting on line 6, is the configuration for the message filtering service.This service can be used to change messages within the server before they make their way to the client. In the instant messaging context, you might use this to block people you don’t want to talk with or reply with a vacation message when you’re away. A filter is a set of conditions and a set of actions. If any of the conditions are met, then all the actions are taken and no more filters are evaluated.The section of the filter configuration (starting at line 9) lists the set of conditions and actions that can be used to make filters.Their meanings are summarized in Tables 2.4 and 2.5. Table 2.4 Message Filter Conditions Condition Example
Meaning
jabber:iq:time
Matches iq packets with the specified XML namespace. Matches whether the recipient’s presence status is unavailable. Matches packets from the specified sender. Matches the recipient’s resource (Work in this case). Matches whether the subject is exactly as specified. Matches whether the body of the message is exactly as specified. Matches the recipient’s presence show tag. Generally this is one of xa, normal, chat, or dnd. Matches the type of the message. Generally this is one of normal, chat, headline, or error. Matches whether the sender is in the recipient’s roster. Matches whether the sender is in the specified group in the recipient’s roster.
me@my-jabber Work Hi There Some message text. xa
normal Friends
Table 2.5 Message Filter Actions Action Example
Meaning
Error Message!
Replies with an error message containing the specified text. Stores the message offline. Forwards the message to the specified JID.
me@my-jabber
Service Configuration Details
Table 2.5 Continued Action Example Thanks! headline
Meaning Sends a reply message to the sender. Changes the type of the message to be as specified. A special action that tells the filter processor to keep processing rules, even though this one matched.
The first element in the configuration (line 7) is called and it’s empty in the stock jabberd configuration. If there are filters in this section, they are applied to every message. So if a mean server administrator wanted to discourage chatting, he could define a default filter like this: chat Get Back To Work!
This would match all chat messages and reply with an error. The Server vCard The section starting at line 33 in Listing 2.10 defines the default identity information for the server. Clients can query the server to ask for its vCard information, so you might want to change this to be more descriptive than the default—maybe something like this: Global Mega Corp Jabber Server Jabber server for internal use only! http://www.globalmegacorp.com/
New Client Registration The section starting at line 39 in Listing 2.10 defines the information required to register a new client with this server. Choose a username and password to register.
The notify=”yes” attribute tells the registration module to send a message to the administrator (if there is one) whenever a new client registers. Administrators are defined a little farther down in the jabber.xml file.
57
58
Chapter 2
Installing and Configuring Jabber Software
The JSM always requires a username and a password, but this configuration also requires a name and an email address. If you want to disallow any new users, you can comment out this section of the jabber.xml file.The current crop of instant message clients seem to universally ignore this information. New Client Welcome The next section of the JSM configuration in Listing 2.10 (starting at line 45) defines a message that is sent to all new users. Here’s another example: Welcome! Welcome to the Global Mega Corp Jabber server
Server Administration The section starting at line 50 in the default configuration in Listing 2.10 is commented out.When uncommented, it enables you to define users that will have “administrative” rights to the JSM.They can’t really change the configuration as you can by editing the jabber.xml file, but they can do things such as get the list of users, send a message to all users, and so on. You can list two types of administrative users: those with read-only administrative access, and those with read/write access. If you look again at the configuration, you can see two administrative users: support@localhost admin@localhost Auto Reply Message was received and forwarded to server administrators.
The user support@localhost has read-only administrative access, so he can receive administrative messages and see who’s online.The user admin@localhost has read/write access, so he can also send administrative messages. Naturally, if you uncomment the section, you need to change these JIDs. All administrative users get messages sent to the server without a username in the address.That is, the “to” address is just the server name with no @ and user name before it. Also, any administrative user can get a list of the currently connected clients by sending an IQ packet like this:
Service Configuration Details
If the sender is an authorized administrator, a document like this will be returned:
The numbers in parentheses after each user are, respectively, The number of seconds the client has been connected The number of packets received from that client The number of packets sent to that client n
n
n
Administrative users with read/write privileges (that is, those that are contained in a tag) can also send broadcast messages.These can be sent to all users when they log in (the message of the day or MOTD) or to all currently online users. A message formatted like this goes to all online users:
This is a broadcast message!
A message like this is sent to all online users and also to all users when they log in: The message of the day.
The Update Section The JSM can be configured to ask another jabberd server whether a new version of the jabberd software is available. A central repository of this information is kept on a server at update.jabber.org.The following line specifies that the update server should let us know if there is a new version of the JSM available: my-server-name
For this to work, your server has to be able to reach update.jabber.org, and has to be able to reach your server.There’s nothing really important lost by commenting this out.
update.jabber.org
vCard Synchronization Each client can store information about itself in its own vCard. Client information can also be stored in the Jabber User Directory (JUD). Including the line, as in line 63 of Listing 2.10, instructs the JSM to send new and changed vCard information to the JUD.This can be safely commented out.
59
60
Chapter 2
Installing and Configuring Jabber Software
Service Browsing This section (starting at line 65 in Listing 2.10) specifies how the JSM will represent services that clients can browse.This is how a Jabber server advertises its capabilities and the capabilities of related services.The default configuration advertises the global Jabber User Directory (JUD) on the users.jabber.org server. Lines 67 and 68 declare that this service can respond to search (the jabber:iq:search namespace) and register (the jabber:iq:register) IQ requests. Clients can use the information in the section to determine how to interact with the service and, if appropriate, what information to display to a user.You can find more details about service browsing in Chapter 5. Component Configuration As mentioned above, the JSM service is made up of several different components.These components are defined in lines 75 through 96 of Listing 2.10.The order of the modules is important, because packets are delivered to modules in the order in which they are listed, and you can disable individual modules by commenting them out of the section.Table 2.6 summarizes each one. Table 2.6 JSM Components JSM Module
Service
mod_echo
Replies with the same message it was given. It replies to messages sent to servername/echo. Handles users’ rosters. Handles requests for the server’s time of day. Manages users’ vCard information. Keeps track of the last time a user logged out. Responds with the server’s version information. Handles the broadcast messages that can be sent by administrators. Maintains backward compatibility with an old method of service browsing. Handles the information defined in the section described earlier. Handles the features available to administrative users. Filters messages as described earlier. Stores messages for clients that are not connected to the server. Handles clients’ presence and availability status information. Handles simple plaintext password login authentication. Handles hashed password authentication. Handles “zero-knowledge” authentication. See Chapter 6, “Jabber Security,” for details about authentication.
mod_roster mod_time mod_vcard mod_last mod_version mod_announce mod_agents mod_browse mod_admin mod_filter mod_offline mod_presence mod_auth_plain mod_auth_digest mod_auth_0k
Common Optional Services
Table 2.6 Continued JSM Module mod_log mod_register mod_xml
Service Logs the end time of users’ sessions. Enables new client registration and manages clients’ registration information. Provides clients a mechanism for storing data on the server.
The configuration of the JSM is by far the biggest section of the default jabber.xml file and its capabilities make up the core of the server’s instant messaging capabilities.
Common Optional Services This section looks briefly at how to configure two common services used to support instant messaging: the Jabber User Directory and the conferencing components.
Jabber User Directory (JUD) As you saw in the default jabber.xml configuration, there is an instance of the JUD running at users.jabber.org that can be accessed from anywhere.You may, however, want to set up your own JUD for use within your own group. It’s easy to install and configure. Note Many of the optional components have multiple implementations. This chapter covers the C versions, but others are available from various sources including http:/www.jabberstudio.org.
First, download the installation package from the same place you got the jabberd server: http://jabberd.jabberstudio.org/downloads. As of this writing, the current version of JUD is 0.4. Because the JUD code refers to code in the jabberd code base, unpack the code into the same directory as your jabberd installation. Unpacking the software creates a new directory called jud-0.4 containing the C source code. Simply cd into that directory and type make: $ cd ./jud-0.4 $ make gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 -fPIC ➥-I../jabberd -c -o jud.o jud.c In file included from jud.c:31: jud.h:33:1: warning: “VERSION” redefined In file included from jud.h:31, from jud.c:31: ../jabberd/jabberd.h:47:1: warning: this is the location of the ➥previous definition gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0
61
62
Chapter 2
Installing and Configuring Jabber Software
➥-fPIC -I../jabberd -c -o jud_reg.o jud_reg.c In file included from jud_reg.c:31: jud.h:33:1: warning: “VERSION” redefined In file included from jud.h:31, from jud_reg.c:31: ../jabberd/jabberd.h:47:1: warning: this is the location of the ➥previous definition gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 ➥-fPIC -I../jabberd -c -o jud_search.o jud_search.c In file included from jud_search.c:31: jud.h:33:1: warning: “VERSION” redefined In file included from jud.h:31, from jud_search.c:31: ../jabberd/jabberd.h:47:1: warning: this is the location of the ➥previous definition gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 ➥-fPIC -I../jabberd -shared -o jud.so jud.o jud_reg.o jud_search.o ➥-ldl –lresolv
You might see some warnings about “VERSION” being redefined, but they can be safely ignored. After this finishes, a new shared library is in the jud-0.4 directory.This is the module that you load by using jabber.xml. Next, add a service section like this to jabber.xml: jud.my-jabber ./jud-0.4/jud.so Global Mega Corp Directory This service provides a simple user directory service. http://foo.bar/
Be sure to replace the contents of the tag following “jud” with your server name.You can also change the vCard information as appropriate. This adds the service, but it would be good for clients to be able to find the service by browsing, so you need to add some information to the JSM configuration. Find the section contained in the tags and add this entry: jabber:iq:search jabber:iq:register
The jid attribute in the tag matches the contents of the tag in the service definition.The JUD service responds to search and register requests, so the
Common Optional Services
and jabber:iq:register namespaces are included with the tags. That’s all there is to it. Restart jabberd and you should be able to use the new service. If you browse to the Jabber server with the Winjab browser, you see what appears in Figure 2.3. jabber:iq:search
Figure 2.3 Browsing the Jabber server.
Clicking on the icon in the right pane browses into the JUD service, and you see a window that looks like Figure 2.4.
Figure 2.4 Browsing the JUD service.
The left pane now contains links that enable you to use the search and register capabilities of the JUD.
Conferencing The conference service component is also available at http://jabberd. of this writing, the current version of the conference component is 0.4.
jabberstudio.org/downloads. As
63
64
Chapter 2
Installing and Configuring Jabber Software
Unpack the archive into the same directory with the jabberd software. It creates a new directory called conference-0.4. Change (cd) into that directory and type make to build it.The build should look something like this: $ cd./conference-0.4 $ make gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 ➥-fPIC -I../jabberd -c -o conference.o conference.c In file included from conference.c:31: conference.h:33:1: warning: “VERSION” redefined In file included from conference.h:31, from conference.c:31: ../jabberd/jabberd.h:47:1: warning: this is the location of the ➥previous definition gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 ➥-fPIC -I../jabberd -c -o conference_room.o conference_room.c In file included from conference_room.c:31: conference.h:33:1: warning: “VERSION” redefined In file included from conference.h:31, from conference_room.c:31: ../jabberd/jabberd.h:47:1: warning: this is the location of the ➥previous definition gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 ➥-fPIC -I../jabberd -c -o conference_user.o conference_user.c In file included from conference_user.c:31: conference.h:33:1: warning: “VERSION” redefined In file included from conference.h:31, from conference_user.c:31: ../jabberd/jabberd.h:47:1: warning: this is the location of the ➥previous definition gcc -g -Wall -I. -I. -I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 ➥-fPIC -I../jabberd -shared -o conference.so conference.o ➥conference_room.o conference_user.o -ldl -lresolv $
The build creates a new shared library in the ./conference-0.4 directory that we can load into the server by using the jabber.xml file. As with the JUD, you need to add a section for the conferencing component. It should look something like this: ./conference-0.4/conference.so conference.my-jabber Public Chatrooms This service is for public chatrooms. http://foo.bar/
Common Optional Services
20 has become available has left is now known as
A couple points of configuration data bear explanation. First, within the tag, there is a element.This means that users can browse for rooms within the conference service. If you were to replace it with , then users would not be able to browse rooms and would have to know about rooms before they could join them. Second, the vCard section is the vCard of the conference component.The value in the tag is the number of messages saved by the server so that clients that enter the room can see the past context of the conversation. In this case, 20 messages are saved and replayed for clients when they enter the room. Not shown is another available tag, , which controls the maximum number of conference rooms that can be created. Although rooms are created whenever a client requests to enter a room that doesn’t exist, it’s also possible to create rooms in the server configuration by adding an entry to the conference configuration like this:
Preconfigured Room
In addition to the tag shown here, you can also set several other properties of the room: —Override the entry/exit/rename messages using , , and elements. —Require that all clients use a nickname. —Prompt clients for a secret password before they are allowed access to the room. —If present, this tag indicates that the clients’ real JIDs should be hidden from the other members of the room. n
n
n
n
Browsing the conferencing service is handled a little differently by the JSM than the other services. It has its own tag type, so rather than adding a section, add a line like this to the section of the JSM configuration:
This advertises the conference service to browsing clients. Now, if you restart jabberd and look at the Winjab browser, you can see the conferencing component, as shown in Figure 2.5.
65
66
Chapter 2
Installing and Configuring Jabber Software
Figure 2.5 More services to browse.
Clicking on the Public Chatrooms icon in the right pane takes you to the JUD service, which looks like Figure 2.6.
Figure 2.6 Browsing the Conference service.
The left pane now contains a link that lets you join the conference.
Instant Messaging Clients Jabber was designed for instant messaging, so even if you’re not doing instant messaging, the clients can be useful debugging tools. One of the best clients for analyzing the data from a server is WinJab. It is an open-source (that is, free) client for Windows computers. It can be downloaded from http://winjab.sourceforge.net. It installs like most Windows applications and puts an icon on the desktop that you can use to start it.
Instant Messaging Clients
Note The next version of WinJab is called Exodus. Exodus looks much the same as WinJab, but has a few new features.
When WinJab starts, it presents a screen to let you specify the properties you’d like to use, including what username you’ll use and to what server you will connect. Figure 2.7 shows the initial configuration screen.
Figure 2.7 WinJab configuration.
Click OK to log in to the server.The next screen is the main messaging screen.Your roster appears on the left, and any messages you have received appear on the right. We did most of the XML debugging for this book with the Debug XML pane in WinJab. Pull down the Tools menu and select Show Debug XML to open up the panel shown in Figure 2.8. All messages to and from the server are printed in raw XML here. You can also click the Send XML button to send arbitrary XML to the server.This is especially useful for debugging services.
Figure 2.8 Debugging XML with WinJab.
67
68
Chapter 2
Installing and Configuring Jabber Software
This example is just a simple exchange of messages, but it shows every detail of the XML documents.
Summary This chapter has detailed setting up and configuring the Jabber service. By now, you should be able to install and set up services and have at your disposal a running jabberd. Understanding how to manipulate and augment the jabber.xml configuration file is important if you want to add capability to Jabber, which is where we are heading in Chapter 5. First, however, you need to complete the other side of the picture by understanding the construction of Jabber clients and how clients communicate with the server (using the Jabber Client Protocol), which is covered in Chapter 3.
3 All About Jabber Clients
A client is to me a mere unit, a factor in a problem. Sherlock Holmes
You can learn a lot from the client. Some 70% doesn’t matter, but that 30% will kill you.” Paul J Paulson, former President, Doyle Dane Bernbach
T
HIS CHAPTER LOOKS AT THE OTHER SIDE of the conversation: the client—the reason that a server exists in the first place. In the second quote, the president of the once powerful DDB&O Advertising Agency seems to be saying that there’s some absolute minimum that you must implement in a relationship with (human) advertising clients. In contrast, there is no minimum that you need to implement for a successful Jabber client. The Jabber client can be pretty simple—anything that correctly responds to some (or all) of the set of XML-based Jabber messages. It can be pretty complex, as are the GUI clients that are commonly available. No rule says how much of the entire Jabber protocol a client must implement. Implement exactly as much as you need to provide the functionality of your application, and ignore (gracefully) the rest. For example, the XML-RPC tunnel shown in Chapter 7, “Jabber and Web Services,” doesn’t implement any message support, nor does it respond to any presence messages. It expects only (infoquery) messages containing XML-RPC requests and responses, transported for clients that are not actually Jabber clients.The picture sharing application shown later in this chapter is a Jabber client that responds to all three types of Jabber messages, but which renders message payloads in a special way.
70
All About Jabber Clients
Finally, what’s exciting about Jabber is that you can implement elements of your client in any language that pleases you—Jabber delivers on its promise to be language and platform neutral. This chapter gives you some rudimentary Jabber clients to build; these will help you understand that creating a client is really a pretty easy exercise.Then it presents a comprehensive reference to the types of messages your client might expect to handle. Finally, it explores the various stanzas of XML used in Jabber messaging.
What Is a Jabber Client? A design goal for Jabber was that it support a simple messaging structure and allow clients ranging from full function GUIs and special applications, down to the trivially simple (for example, even something as simple as a Telnet connection on the jabberd port, as you will see shortly in the first examples in this chapter). Architecturally, Jabber imposes very few restrictions on clients.The only things a Jabber client really has to do are Communicate with one Jabber server over TCP sockets. Parse and interpret XML “stanzas” over an XML stream. (The meaning of “XML Stream” is explained shortly.) Understand a small set of core Jabber XML message types. n
n
n
Session Mechanics It’s always been our opinion that the closer to the socket layer an application can get, the more efficient your application can be. A Jabber session is really just pairs of full duplexed sockets—one at the Jabber server for each client, one at each of the clients. (Actually, it’s better to think in terms of endpoints rather than clients because endpoints can be anything from traditional conversational client GUIs to Internet services.) The down side is that you will have to do a lot more management of the conversation. By custom, a Jabber session is a TCP session on port 5222 (or 5223 if SSL encryption is turned on). As Figure 1.1 shows, a session is in effect until the socket is closed by one of the parties. A session can be conceived as a bi-directional stream of XML constantly being analyzed for content and correctness by either clients or servers. Structurally, a client really is an XML stream reader in the manner as a typical application built on an XML SAX or stream parser, where the application’s actions are event driven—events being triggered by tokens in the received XML stream.
Protocol Mechanics As we have talked about before, within the context of XML messaging, Jabber’s open XML protocol contains only three top-level XML elements:
Protocol Mechanics
(carrier for ‘ordinary’ conversational elements) (carrier for presences and availability messages) (carrier for informational and query messages)
With these elements, a client can create messages that accomplish all the design goals of an instant messaging system and more. Note As you’ll see in later chapters, the Jabber server uses other XML elements for system management, but these three are the ones clients use most.
You can think of these message types as the basic structure of the carrier of concepts between users, applications, and between Jabber message switches.They are the equivalent of spoken language rules whereby words or other elements of sentence structure are combined to form grammatical sentences. You may not have realized it (because we haven’t discussed it yet) but all these asynchronously generated and arriving XML stanzas with presence, conversation, and queries don’t simply appear at the switch completely out of context, as it were. As shown in Figure 3.1, from a client standpoint, a session with the Jabber switch occurs in the context of an XML stream.This stream may be viewed as similar to a telephone conversation where a “hello” and “goodbye” are the signals for the beginning and end of a conversation. Everything in between is the body of a conversation; whether it’s about the impending snow storm or a stock transfer is immaterial from the standpoint of the switch. It just wants well-formed “sentences” in the stream.The tagging of each individual message will aid the switch in routing your conversation. Of course the jabberd is handling many simultaneous streams from potentially many clients at the same time. As depicted in Figure 3.2, some of your messages are destined for other clients, and the switch has to be able to route those on your behalf. The switch can also, on its own initiative, send messages as a proxy for you. For example, as Figure 3.2 suggests, when you go offline, a presence message gets sent to other clients, some of whom are on your roster and who want to alert their users that you are no longer available.Thus, within the possible conversations between a client and a server, some messages are relevant to setting up and maintaining a relationship with the server, and some are concerned with maintaining the conversational context between clients, but all must be expressed in the syntax covered in this chapter and all are contained within an XML stream that begins with a tag and officially ends when the server gets the matching tag.This all makes some sense when you remember that XML documents can have only a single root tag.Thus everything sent to or via the server must be contained within the confines of an outermost tag—the pair.
71
72
All About Jabber Clients
ON Off
ON Off
Figure 3.1 A conversational stream between a single client and the switch. Jabber message switch
ON Off
Figure 3.2 Serving multiple clients.
Protocol Mechanics
To prove that this is so and that we are not just spewing nonsense, write a little shell script that does the equivalent of a “Hello world” programming example. In an editor buffer, type this in:
If your development Jabber server is somewhere other than localhost, then replace that bit with the correct information. Also, type this in on the next line:
Next, start a shell to run a Telnet session as shown in Figure 3.3.
Figure 3.3 Telnet “Hello World” example.
Make certain that you turn on local echo as shown in Figure 3.3 so that you can catch the output returned by the jabberd message switch. Next, cut the first line from your editor buffer and paste it into the Telnet window, as shown in Figure 3.4. Note We didn’t recommend typing in the initial XML because we can’t type that many characters without an error; if you’ve a steady hand, however, you could have done so. Also note that there is a time limit imposed by the server for receiving data and logging in. If you fail to meet that time limit, it boots you off. That’s another reason not to hand type it.
As shown, the server should return back the string:
To which you reply (either by typing or pasting),
73
74
All About Jabber Clients
Figure 3.4 Completing “Hello World.”
Essentially, this is the equivalent of ringing the phone, waiting for an answer, then ringing off. The answer you got from the server contained an id field with some hex digits in it. Notice the importance and varying uses of the id as you go through the following examples. To expand the “Hello World” example, let’s exercise one of the three acceptable message types.This time after opening a session with the tag shown earlier, let’s ask the server what it takes to register a new user by pasting in the following “sentence,” which is an XML stanza whose type is a ‘get’—we’re asking for information.
The server returns an message with the same ID you used to make the query, whose type is ‘result’: Choose a username and password to register with this server.
This is a sentence in Jabber-ese explaining that if you’re going to create a client in these here parts, you’re going to have to supply a name and password, a nickname (username), and an email address. Now you can paste back the following into your Telnet window:
Protocol Mechanics
Praline Cleese Mr_Praline
[email protected]
The Jabber server responds back to you using the same ID, and tells you that you are good to go as a new user:
If you were writing a client in a programming language instead of shoving some XML in front of the server’s face with Telnet, you could probably imagine that your register user method would jam the outgoing request into a dictionary structure keyed by id, put yourself into an idle loop, and then whenever the server got around to responding, you would parse the XML out of the continuously arriving stream, match up the response and see whether you were successful or not.You can imagine once again that if this were code, the iq ‘result’ should be interpreted by your client as ‘success’. On the other hand, if you tried a second time to register, the server would slap your virtual wrist by responding as follows: Praline Cleese Mr_Praline
[email protected] Username Not Available
This is what you would fervently hope (if you were Mr. Praline) that the Jabber server would do. Now assume you’ve now logged in successfully as Mr. Praline by opening a Telnet stream, pasting in the opening tag , and pasting in a login request: Praline Cleese telnet
75
76
All About Jabber Clients
You got back the hoped for
from jabberd. Now say you have some buddies on your roster, which you could have created via a set of -based messages.We’re not going to show that here, as we want to keep things simple for the sake of illustration. Let’s inject a into the stream and see the effects; simply paste in or type:
This forces the server to take a peak at your stored profile (in this case and shove a roster back at you:
praline.xml)
available 0 available 0
Finally, let’s say you wanted to re-enact the infamous Monty Python “Dead Parrot” sketch online. Here, you would use the final type of xml stanza type, . Once again, given that you have opened a Telnet to your jabberd on port 5222, have gotten the server’s attention with a starting tag, and are logged in with an tag shown earlier, you can send a message: I wish to complain about this parrot what I purchased not half an ➥hour ago from this very boutique.
Assuming dana is using one of the several real clients and not just typing in XML stanzas as you have been doing, a chat window pops up on dana’s client (see Figure 3.5), and away you go with the dialogue. What you actually see in the more primitive Telnet window looks like this, however, with a message ID and thread ID generated by dana’s client (so that it could track subsequent messages on the same topic—at least as the concept of “topics” is understood by the communicants). Note too that the apostrophes are escaped as you would expect to see in a proper XML stream: A client and the server must deal with proper XML construction.
Protocol Mechanics
Figure 3.5 Using the “message” XML stanza. fd6d59abf7970b853d09580693569290739a80ae Oh yes, the, uh, the Norwegian Blue...What's,uh... ➥What's wrong with it?
If you wanted to respond, you might type into the Telnet stream: I’ll tell you what’s wrong with it, my lad. ‘E’s dead, that’s what’s ➥wrong with it!
More about the id attribute and the tag later. After a bit more of this, you would end the conversation with the server by typing in the closing tag. If you were actually typing in the example, you might have noticed that as soon as you typed in the final > character, the Telnet session closed immediately.The Jabber server reads every character sent to it and uses an eventdriven XML parser. As soon as the “hang up” event is fully detected—poof, your session is gone.
77
78
All About Jabber Clients
Protocol Details In the very crude client constructed earlier we used all the client-to-server elements and demonstrated a large percentage of the simple things a client can do. Let’s look in greater detail at the elements we used so that when you write some more sophisticated clients, you will understand what elements are important with what messages.
The Tag Let’s start with the tag. As we said earlier, this tag has as potential elements everything in the conversational stream. Additionally, as you may have noticed, it also has some attributes. First of all, the tag identifier has a kind of funny notation, stream:stream.This basically says that this is a stream tag of the type defined and qualified by the namespace stream, whose definition can be found at the URI http://etherx.jabber.org/ streams.The little piece of required encoding in the tag that reads xmlns:stream= ’http://etherx.jabber.org/streams says precisely that. Notice that it is required to be in the tag exactly as encoded. The to Attribute This attribute specifies the virtual host within the Jabber server. Multiple entries are allowed in the jabber.xml configuration file—each one is for a separate virtual server. It might be that if you’re planning a relatively big farm of jabberd servers, that you start out by specifying them on the same physical hosts, then when it runs out of gas, move each virtual server to a separate physical server in the future.The to attribute in a tag therefore refers to the virtual server for your particular client. Jabber servers can of course resolve names to DNS addresses for other Jabber servers, so you can spread servers over hosts as load growth demands.
The Element The login example showed a number of uses for the tag, and it acted as a container for both outgoing requests (from the client to the server) and incoming responses (server to client). Note that in contrast to certain other protocol structures, the info/query streams are “in band” with other message types and can occur asynchronously. A nice side effect of completely in-band protocols is that unlike email, where registration is a completely separate and distinct process, a new user can register with a reachable Jabber server on the fly. Other than the asynchronous nature of requests and responses, the mechanism is like HTTP-GET or -PUT: It enables two entities to make requests and receive responses. An info/query stanza may possess the following attributes, though not all are required
The Element
or even appropriate in every query conversation, and might reasonably be ignored by either participant in the conversation: to—Specifies the intended recipient of the query stanza.This is optional for most query types, as for example when we asked for the registration information from the server earlier or even when we sent back our registration information. In the first case, the jerry-rigged “client” just wanted some information from the physical jabberd to which we Telneted. It would have been superfluous to send a specific to entity. In both cases, the client had a really good idea of the identity of the intended recipient. Equally, when the Jabber server responded, it had a good idea of the physical entity to whom it was responding (and no idea of a jabber-id because one didn’t yet exist). It might be, however, that the info/query is not destined for the jabberd itself but rather for the account setup portion of your news and sports delivery component, in which case the switch will definitely want a destination address to which the query stanza should be routed. from—Specifies the sender of the query stanza.This is optional for most query types, as for example when we asked for the registration information from the server earlier, or even when we sent back the registration information. Because the user didn’t exist at either of those points in time, it would have been strange (actually incorrect) for the server to expect a jabber-id.The same holds true of the server’s responses. Again, it might be the case that the info/query is not destined for the jabberd itself, in which case the sender’s JID might be important.The from=’’ attribute is checked by the server to see whether it matches your user@server. If that part does not match, then it overwrites the from=’’ you sent from the Client with the JID that you used to authenticate (that is log in).This enables you to control your resource, but you cannot SPAM other users by saying that you are someone else. id—An optional unique identifier for the purpose of tracking the requestresponse interaction.The sender of the query stanza could set this attribute, which may be returned in any replies. As you may have noticed in the previous examples, you could type in any valid string for an ID.The responder used the same ID to let the sender know to what the response referred.Writing a real client, you would probably want to use a random number generator to assure uniqueness for query ID. n
n
n
n
type—This
attribute is required in all queries and specifies a distinct point of interaction.Transactions and responses generally follow the pattern shown in Figure 3.6.
79
80
All About Jabber Clients
Jabber message switch ON Off
Figure 3.6 Common C2S queries and responses.
The type Attribute If you look back at the example client-to-server (C2S) interactions, you will have noticed that the dialogue involved in a query is actually pretty simple, and is defined by the type attribute in the tag.You can either “get” something (information usually) from a Jabber server, or “set” something at the switch. Normally, a “set” sends stuff to the switch, which will persist for you in the .xml data file it stores on your behalf.What you get back is either a “result” or an “error.” Any query-capable client you write therefore simply has to know how to generate queries and tell the difference between good and bad results. You saw an example of a “get”-type query in the request for registration information earlier, and a “set”-type query when registration information was supplied to create the user.You saw “result” and “error” responses when we successfully registered a new user and then tried to register again. The value of type must be one of the following (anything else is ignored): get—The stanza is a request for information.The details of the request will be found in the child element for the tag.The child element must be qualified by namespace. For example, a request for registration information uses the xml namespace jabber:iq:register. A login request uses jabber:iq:auth.The get queries shown in the C2S example in Figure 3.6 expect to see one of the results returned. set—The stanza contains data intended to provide required data (most likely specified in a previous returned result, set new values, or replace existing values. The ‘get’ queries shown in the C2S example in Figure 3.6 expect to see one of the results returned. n
n
The Element
n
result—The
stanza is a response to a successful get or set request.The result might have a single XML child element with enumeration and/or clarification of data required in a subsequent set query.The single XML child element is a tag pair. In the example that requested registration information, the server returned a with several children, with each tag representing a required bit of information or a clarification (the element) suitable for display out to a human reader: Choose a username and password to register with this server.
n
error—Means
that an error has occurred in the processing (or delivery, in the case of a message being routed to some other entity) of a previously-sent get or set. The returned stanza must include a single child element, which in turn must contain a numeric code attribute corresponding to one of the standard error codes. It could also contain an optional text string as element text corresponding to a description of the error. In the example where we tried to double register “Mr. Praline,” the server returned an whose type was error and whose child element was Username Not Available. A complete list of error codes is shown in Table 3.1.
Table 3.1 Complete List of Jabber Error Codes Code
Description
302 400 401 402 403 404 405 406 407 408 409 500
Redirect Bad Request Unauthorized Payment Required Forbidden Not Found Not Allowed Not Acceptable Registration Required Request Timeout Conflict Internal Server Error
81
82
All About Jabber Clients
Table 3.1 Continued Code 501 502 503 504 510
Description Not Implemented Remote Server Error Service Unavailable Remote Server Timeout Disconnected
Tip If you’re familiar with HTTP, you’ll recognize the numbers in Table 3.1 as the same as HTTP status codes.
Data and Namespaces in Elements A key point is that the data content of both requests and responses is qualified by the namespace declaration of a single direct child element of the element.That is, to get a definition of what the data mean, you can use the namespace to clarify.The types of topics that can be included in an query are qualified by different namespaces that can be included in the tag.When we logged in with the following: Praline Cleese telnet
the meaning of the children tags of the tag are explained in the jabber:iq:auth namespace. If you are an XML purist we should say that an element can have only one legitimate child, the tag. Otherwise however, whenever there’s a element, the children are always XML from another namespace. An info/query stanza can contain any properly-namespaced child element. Even a simple result might contain such, although it might be unusual. Implement only what is truly necessary in your client and forget the rest. A complete list of all the namespaces relevant in Jabber, including those used in info/queries, is shown in Table 3.2.
The Element
Table 3.2 Complete List of Jabber Namespaces Mnemonic (from jabberPy library)
Namespace
NS_CLIENT NS_SERVER NS_AUTH NS_REGISTER NS_ROSTER NS_OFFLINE NS_AGENT NS_AGENTS NS_DELAY NS_VERSION NS_TIME NS_VCARD NS_PRIVATE NS_SEARCH NS_OOB NS_XOOB NS_ADMIN NS_FILTER NS_AUTH_0K NS_BROWSE NS_EVENT NS_CONFERENCE NS_SIGNED NS_ENCRYPTED NS_GATEWAY NS_LAST NS_ENVELOPE NS_EXPIRE NS_XHTML NS_XDBGINSERT NS_XDBNSLIST
jabber:client jabber:server jabber:iq:auth jabber:iq:register jabber:iq:roster jabber:x:offline jabber:iq:agent jabber:iq:agents jabber:x:delay jabber:iq:version jabber:iq:time vcard-temp jabber:iq:private jabber:iq:search jabber:iq:oob jabber:x:oob jabber:iq:admin jabber:iq:filter jabber:iq:auth:0k jabber:iq:browse jabber:x:event jabber:iq:conference jabber:x:signed jabber:x:encrypted jabber:iq:gateway jabber:iq:last jabber:x:envelope jabber:x:expire http://www.w3.org/1999/xhtml jabber:xdb:ginsert jabber:xdb:nslist
83
84
All About Jabber Clients
Jabber Presence The element has a number of attributes and subelements which we’ll present in practical usage first, then document more formally. We presented an instance of this tag earlier in the Telnet session when we suggested you try typing in a single-line stanza consisting of only a element. Remember that the Jabber server returned a number of elements in response.The primary purpose of this element is the synchronization of presence and availability among peers who care. By definition, and in every switch implementation, this means each member of a client’s roster. Normally, the switch may cache this list, but because the in-memory list is a clone copy, it may refer to the persistent stored version for reliability as needed. Notice that you didn’t need to add to or from’ attributes to your initial presence message.The server’s automatic reaction upon receiving an otherwise unqualified message is to read your roster from its external persistent store and do two things. First, it gets the presence of the members of your roster list by sending them presence messages with an attribute of probe. For example:
If the server can’t connect with the probed user, then it simply drops the packet. Because virtually every GUI client initializes with client’s entire roster shown as “offline,” the portrayal of roster items changes only on arrival of an appropriate message. Second, if the server does connect to a member of your roster, the delivery of a presence-probe message to the client stimulates the client to reply with its complete presence state.What gets returned to you may vary from client to client.The server delivers that message back to your client, which then updates its GUI.You also did not have to specify a status element on your initial presence message.The server assumes a state of “available” for you. This “whatever happens, happens” brand of interaction is illustrated in Figure 3.7.The server receives a simple message from Client A, reads Client A’s roster and expands, then sends presences messages to Client B and C. B responds with a packet addressed to Client A, which then updates its GUI, but no response is ever received from Client C. Of course, when Client C finally does come online, it will send a packet to Client A, to which Client A will respond; on response, Client C updates its GUI. You’ve seen how the server mediates on behalf of Client A to multicast its presence to its roster. So how does Client A get updated with Client C’s presence when Client C comes online, or from Client B when its presence changes? The user at Client B might, for example, explicitly set a presence using the client GUI, or Client B might just disconnect.
The Element
ON
Off
GUI Update for ‘B‘
No GUI Update for ‘C‘
c l i e n t
STOP c l i e n t s
S e r v e r “
“ A”
”
B “c”
Figure 3.7 Presence probes and response interactions.
In both cases, the server actually does the honors, as one client doesn’t normally send presence packets directly to another (although it’s possible to bypass the server’s Jabber session management—JSM—module if both parties are subscribed to each other’s presence). Because Praline@localhost and jane@localhost are subscribed to one another, Mr. Praline can, after login, simply generate a packet like this: returning dead parrot 0
and toss it over the wall to the server. If you have debug turned on at the server (for example, by running the server from the command line with the –D option), as in .\jabberd -D -H .
you’ll first see the routing request being generated by the server, then the following: deliver.c:257 deliver( to[jane@localhost/Home],from[Praline@localhost],type[2],packet[
85
86
All About Jabber Clients
returning dead parrot 0 ])
The server is generally quite happy to route something for you, so it wraps the request in a packet and delivers it to
[email protected] problem is (as you’ll notice if you run this experiment) that jane@localhost does not respond with its presence.You are short-circuiting the presence and availability mechanism, so this is not a wise way to write a client. Rather you should rely upon the subscription mechanisms in the Jabber server to make the necessary circuits for you. To allow the server to manage subscriptions for a client pair, the two clients must mutually agree to subscribe to each other’s presence and availability. One client can’t just probe another client to whom it’s not subscribed.Try it using the Telnet approach you saw earlier to generate a presence probe, in this case from Mr. Praline to bill@localhost (these two are not cross-subscribed):
The server responds by dropping the packet, as shown in the following server trace: Sun Feb 23 14:39:59 2003 mod_presence Praline@localhost attempted to probe ➥ by someone not qualified
You can, however, probe someone with whom you have agreed to share presence information. Here, praline@localhost and jane@localhost have both agreed to share presence, so Mr. Praline can type in (in a Telnet session):
And now the server allows the connection. In response, jane@localhost replies: available 0
There are some interesting subelements in the response packet.The … bit you have already seen. Generally, clients return either a pair or a pair (but not often both). Either contains CDATA, which can be any free text, as we document later.There’s also a tag, which is used to help distinguish one presence from another: It may be the case that you are logged in to a particular Jabber server on two different machines; a higher priority value in the element suggests to the server that this is the client to preferentially route packets. Finally, the bit is a time stamp that a client can use to determine the vintage of information. In this example, the available status was good as of about 7:42 p.m. (time-relative to the server).
The Element
Notice that the entire series of conversations takes place asynchronously and relies on each party in the conversation to do its part.What impresses us as especially clever and in keeping with the overall design philosophy is the asynchronous nature of so many things in Jabber.There is very little need for the Jabber server to keep huge amounts of state information for clients.The switch simply attempts to act as a switch or a router. If it’s unable to route a message, then it allows the failure to happen.When clients are offline, their information is persisted into an XML file, including their roster contents, and (optionally) their preferences.When a client returns online, the Jabber server reads in the persisted XML file, whose format we covered in Chapter 2, “Installing and Configuring Jabber Software,” using its XDB capability, and acts upon what it finds (particularly roster information) there.The switch follows to the rule, “Do only what’s required and no more.” Strict adherence to this rule makes for a lean and mean switch, both functionally and footprint-wise. Notice too the division of labor among parties in Jabber.The server merely delivers the presence-probe to the clients on your roster.What they choose to do in response to the probe is not the server’s concern. Clients most often respond by sending a presence packet—indeed that’s the expected behavior.When a client sends a responsorial presence packet, the server simply delivers it to you without having to remember that this packet is somehow connected with something that it sent to you previously. Note We aren’t going to cover the intricacies of writing a complete server in this book, but understanding these concepts will help you should you decide to use the open source Jabber server as a way of getting started toward building your own or making your own contribution to the open source implementation.
Presence Attributes In the primitive example at the beginning of the chapter, the presence message was unmodified by additional attributes, but there are a few worthy of mention that you need to put into your programming toolkit. Let’s formally talk about them now.
The Element In addition to being used as a carrier for clients’ presence and availability, the element is also used by the server as a management tool for connected clients.The element is also used to negotiate and manage subscriptions to the presence of other entities.You have seen some practical examples of using presence already, but to formalize your understanding, you should look at some of the attributes of the tag itself.
87
88
All About Jabber Clients
The Attributes A presence element can have several attributes, not all of which are appropriate to a given purpose. to—Specifies the intended recipient of the presence stanza (if any). Often, this attribute is filled in by the Jabber server rather than specified by a client. A message with no other qualification attributes is normally issued when a client session connects.This is expanded by the Jabber server into messages for each of the client’s roster members. After it is expanded by the server, a message contains both to and from attributes. Examples were shown earlier in the section “Protocol Mechanics.” from—Specifies the sender of the presence stanza. Normally this is reflected back to a sender as a part of a response to a presence multi-cast managed by the Jabber server’s session manager module. Again, the from attribute is verified against your JID and overwritten if the user@server does not match the JID with which you logged in. Usually it’s safest to not send it. id—A unique identifier for the purpose of tracking presence.The sender of the presence stanza sets this attribute, which may be returned in any replies. Although it is permissible to include an id attribute, it has no value for presence management. type—Describes the availability state, subscription request, presence request, or error.When there is no type attribute there is an implied value of available. When specified, type should have one of the values shown in Table 3.3. n
n
n
n
Table 3.3 Complete List of Jabber Type Attributes Value
Significance
available
This is the default availability value.To signify a presence of “available,” don’t set a type attribute at all in the outgoing packet. The sending client (identified in the from attribute) is no longer available for communication.This likely will have been sent by the Jabber server (on a client’s behalf) when a client disconnect was detected. This attribute is normally issued by the Jabber server on behalf of a client and addressed to the clients on the originating client’s roster.The normal form is:
unavailable
probe
subscribe
A resource may be specified too (as in to=’user@server/resource’, and is not ignored if present. A client may do its own probes (only to clients with which it has mutually agreed to share presence information—avoids wanton misuse of the capability). The message must be fully qualified with from, to, and type=probe attributes. The sending client (identified in the from attribute) wishes to subscribe to the recipient’s presence.The recipient is identified in the to attribute.
The Element
Table 3.3 Continued Value
Significance
unsubscribe
The sending client (identified in the from attribute) wishes to unsubscribe to the recipient’s presence.The recipient is identified in the to attribute. subscribed The sending client (identified in the from attribute) agrees to allow the recipient (identified in the to attribute) to access its presence information.The recipient originated the conversational exchange via a presence message whose type was subscribe. unsubscribed The sending client (identified in the from attribute) acknowledges an unsubscribe request from the recipient (identified in the to attribute) to access its presence information.The recipient originated the conversational exchange via a presence message whose type was unsubscribe. error An error occurred in the server’s processing or delivery of the presence stanza.
The Subelements A presence element can have four distinct stanzas as subelements, not all of which are needed in a particular situation. —This stanza describes the availability of a client in terms of specific values. Other values are ignored. A element typically qualifies a element whose type attribute is available. A presence element usually contains a child element or a child element but not both, as shown in Table 3.4. n
Table 3.4 Complete List of Jabber “Standard” Presence Codes Value
Significance
away chat
The client specified in the from attribute is temporarily away from the keyboard. The client specified in the from attribute is available for “chat.” Generally superfluous, as an available client is assumed to be available for chat. The client specified in the from attribute has marked its availability as “do not disturb” (dnd = “Do Not Disturb”). The client is available. Once again, generally superfluous. The client specified in the from attribute is away from the keyboard for an extended period of time. (xa == “eXtended Away”).
dnd normal xa
n
—An
optional natural-language description of availability status. Normally a client issues this as an explicit expression of availability: “I am in a meeting,” “I am writing,” and so on. It is used in conjunction with the show element to provide a detailed description of an availability state (for example, “In a meeting”).
89
90
All About Jabber Clients
n
—The
primary purpose of the priority stanza is to suggest to the server which instance of a particular jabberId@jabberServer is the “best” or “first amongst equals” to which a message ought to be routed.Why is this important? Well, suppose you’re logged in at your work desk as
[email protected], but like many of us you are managing by walking around and you are also logged in on your PDA as
[email protected]. Now suppose
[email protected] wants to send you a message.To which presence does the server choose to route a message? If the message is fully qualified with jabberID@jabberServer/Resource, then this might certainly be used to disambiguate. But what if Sam-I-Am is not too brilliant and just addresses a message without a resource? This is where priority comes into play. Clients can set a priority (a non-negative integer) to represent the priority level of the connected resource, with zero as the lowest priority.The higher the number, the more preferred the resource is. Note that although the specification allows a negative priority number to mean that the sender should not be used for direct or immediate contact, not all clients or servers implement this control. Almost all known GUI clients have a control panel to allow setting this value as shown in Figure 3.8.
Figure 3.8 Setting presence priority via GUI.
n
—If the server routes a message that includes a type=”error” attribute, the presence stanza must include an
child element, which in turn should contain a numeric code attribute corresponding to one of the standard error codes listed in Table 3.1 and may also contain PCDATA corresponding to a natural-language description of the error.
The Element
Finally, note that a message might also contain a namespace-qualified subelement.You as a developer of a client can choose to either ignore these or parse the ones important to your application.
The Element The message element is the unglamourous “foot soldier” of all the message types. Its attributes and child elements are relatively easy to understand. A minimal message must contain the address elements as attributes on the message tag itself.The to attribute must appear, and a subelement: hi
Again, in a Client, the from is verified against your JID and overwritten if the user@server does not match the JID with which you logged in. It is not incorrect to set and send it, but it is usually best to not send it at all. Assuming that the two clients were connected to localhost, jane@localhost would receive a message from dana@localhost, and this would result in a message rather than a chat session because no type field has been specified. If a chat stream had been opened by one of the end-points specifying type=chat as an attribute on the message tag, then a chat window is opened and the message becomes slightly more complex (see Figure 3.9).
Figure 3.9 Typical message content in a GUI.
In the first message exchange, the chat session is initiated by setting the message type to chat (type=’chat’). A thread is established (the thread subelement) to uniquely tag the session between the two clients. More about the subelement briefly. 9164572e818c2845f73b442cbc62e1ccf6c3cb48
91
92
All About Jabber Clients
how's the sailing today?
For completeness’ sake, let’s identify the attributes and subelements of formally.
The Attributes A message element can have several attributes, not all of which are appropriate to a given purpose. to—Specifies the message’s intended recipient.This attribute is populated by the client initiating the dialogue. In general the JabberID, which is the value on the right side of the equal sign token for the to attribute, is of the form user@server/ resource.The form user@host is often seen too, though, and either resolves to user@host/resource by the Jabber server if the recipient is online, or is directed to offline storage if the user is not online.This attribute is required for all elements.We’ve shown several examples already in this chapter. from—Specifies the sender of the message stanza. Normally this is reflected back to a sender as a part of a response to a presence multi-cast managed by the Jabber server’s session manager module. In general the Jabber ID for the from attribute must be of the form user@server/resource; however user@server is also acceptable. It may happen that the server might modify or replace the value of this attribute (for example to prevent certain kinds of spoofing), but it’s normal practice for you to insert this element in your own client code because it is required for all elements. id—A unique identifier whose value is a string generated by the Jabber client or client library, and is used by the client to identify the message for tracking purposes (most commonly to correlate messages sent to a group chat room with messages received from the room, or to associate a previously sent message with any errors it might generate).The id attribute is optional. type—Qualifies a message and gives “hints” to a client about what sort of visual interface might be appropriate to display the message. Although the method of portraying the message view is entirely left up to the client, almost all GUI clients provide a different view based on message type. Most interfaces provide an ongoing thread view for chat and groupchat interchanges, and a more “email-like” view for the default message type. For contrast, though, see the unique Lluna client (see “Resources” in Appendix C; see http://www.jabber.org/user/clientlist.php for a comprehensive client list), which uses animated avatars on a Web page.Table 3.5 lists the valid values of the type attribute. n
n
n
n
The Element
Table 3.5 Complete List of Jabber Type Attributes Value
Significance
no value or not present
When nothing is specified, this is a hint that suggests that the receiving client should consider the message as possibly having a “subject” and message “body.” Often, clients present this in a separate non-modal window. This is a hint that suggests that the receiving client should display the message in a typical line-by-line, rolling log chat interface (although the exact interface may be client-specific). This is a hint that suggests that the receiving client should display the message in a “chatroom”-style interface, most famously seen in AOL/AIM or IRC implementations. Enables you to encapsulate a news article with a title and body sub element. Uses the namespace tag. Generally this indicates a response message from the Jabber server. If the value of the type attribute is error, the message should contain an subelement.This subelement is more fully described later in this chapter.
chat
groupchat
headline error
The Subelement The stanza is a child element of that carries the message’s content. It must not carry any tag attributes. As seen in the earlier “Dead Parrot Sketch” example, notice there’s something interesting going on in the actual message data itself contained in the tag pair—more about that briefly. Finally, there is an inclusion of XML content from an external namespace (jabber:x:event).This namespace is a standard Jabber namespace that is used to request and respond to message events relating to the delivery, display, and composition of messages.We showed the Jabber namespace more fully in Table 3.2 (earlier), but basically it is one of four events, listed in Table 3.6, that either a client issues when sending a message or that the server issues on a client’s behalf. Table 3.6 jabber:x:event Attachments to a Message Value
Significance
Offline
Indicates that the server has stored the message offline because the intended recipient is not available.This event is to be raised by the Jabber server. Indicates that the message has been delivered to the recipient.This signifies that the message has reached the Jabber client, but does not necessarily mean that the message has been displayed.This event is to be raised by the Jabber client. After the Jabber client has received the message, it may be displayed to the user. This event indicates that the message has been displayed, and is to be raised by the Jabber client. Even if a message is displayed multiple times, this event should be raised only once.
Delivered
Displayed
93
94
All About Jabber Clients
Table 3.6
Continued
Value Composing
Significance In threaded chat conversations, this indicates that the recipient is composing a reply to a message that was just sent.The event is to be raised by the Jabber client. A Jabber client is allowed to raise this event multiple times in response to the same request, providing that a specific sequence is followed.
Therefore, in your code, whenever you add a jabber:x:event extension to a element, your code as the message sender can track stages in the delivery of that element to its recipient. As shown in the “Dead Parrot Sketch” example, it merely says that the recipient can potentially expect more data later. XML CDATA in the Subelement In Figure 3.9, when jane@localhost replies to dana@localhost, she says: “I’ll send you the Annapolis .” However, when the client generates the message, this is what the message body looks like in raw form: 9164572e818c2845f73b442cbc62e1ccf6c3cb48 I 'll send you the Annapolis <weather-report-url>
What’s going on here? Well, remember that Jabber is XML-based messaging. Therefore the CDATA carried in the message body must properly escape the apostrophe, the quote symbol, the ampersand, the less-than symbol, and the greater-than symbol.Thus your client code can’t just send an unexamined message body to the peer on the other side of the conversation. Additionally, the receiving peer must decode the escaped XML as well. Just remember that everywhere in your code that you prepare text for transmission, you need to translate occurrences of special XML characters(&, , and so on) appropriately, and whenever you want to get the textual data from an XML node (for example, the of a message), you do the opposite. Fortunately, if you’re creating an application atop a client library, the library creator already thought this through for us. For example, in the Python JabberPy package (see “Resources” in Appendix C), the XMLStream handler maintains two simple methods: def XMLescape(txt): “Escape XML entities” txt = replace(txt, “&”, “&”) txt = replace(txt, “”, “>”) return txt def XMLunescape(txt): “Unescape XML entities”
The Element
txt = replace(txt, “&”, “&”) txt = replace(txt, “<”, “”) return txt
If you’re writing a client from scratch in, say, Python or Java, you should do likewise. Here’s another snippet from JabberPy that shows transformation of XML to a string representation suitable for display (for example, in a debug window). Notice that it recurses on itself (line 16), works across namespaces (lines 4-7), and constructs a string representation with escapes properly handled (lines 10, 15, and 18). 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
def _xmlnode2str(self, parent=None): “””Returns an xml ( string ) representation of the node and it children””” s = “” cnt = 0 if self.kids != None: for a in self.kids: if (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt]) s = s + a._xmlnode2str(parent=self) cnt=cnt+1 if (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt]) s = s + “” return s
Additional Subelements In addition to the subelement, a message stanza may contain zero or one of each of the following stanzas as child elements (which may not contain mixed content): Note In actual fact, a is not even required to have a subelement; The XML specification requires zero or one subelement. However, it seems to us that it wouldn’t be much of a message without saying something—a bit like a sentence consisting of a single period.
n
—The
message’s subject.The
tag cannot contain
attributes. Chapter 10
95
96
All About Jabber Clients
How is chapter 10 coming along?
n
Normally if the message’s type attribute is set to chat, the client does not provide a capability to set a subject, although nothing in the protocol forbids this. It’s simply an idiosyncracy of chatting versus messaging. —A random string that is generated by the sender and that may be copied back in replies, although nothing in the protocol absolutely demands that it must be. It is used for tracking a conversation thread.The element can not contain attributes. 0ef291b610e9ec253f0970ebdb0477e1574a4e32 The chapter is going fine
n
In customary client usage, whenever a GUI chat window is closed by either client, a new chat message will generate a new thread value. —If the message contains an attribute where type=”error”, the stanza must include an child, which in turn must have a standard code attribute corresponding to one of the standard error codes shown earlier, and could also contain PCDATA corresponding to a natural-language description of the error. This is demonstrated in the following example, in which client code mistakenly sends a message to a non-existent user (dana@localhost sends a message to william@localhost instead of the correct bill@localhost): hi Not Found
n
(external) stanzas—A message may also contain any properly-namespaced child element (other than the common PCDATA elements, elements, or children of elements).This is one way of extending the element in an arbitrary way. An stanza can also be used as a protocol element to send commands from server to client or from one client to another. Each time the element is used, a relevant XML namespace must be specified. A single message may contain multiple instances of the subelement. The namespaces most often occurring in a message with an subelement are listed in Table 3.7.
The Element
Note The tag is not just a child of . It is commonly used here, but current work and research is also moving to embedding it in , , and often tags.
Table 3.7 Common Extended Namespaces in a Namespace
Use
jabber:x:autoupdate
Possible use: Enable arbitrary client-to-application queries about any software updates or version changes available. Used to provide timestamp information about messages and presence information. Conveyed on presence responses and stored for later delivery when sent to an offline Jabber client. In the latter case, as the client comes back online, this namespace includes information that enables the Jabber client to display the time when the packet was originally sent. Message example:
jabber:x:delay
Looks like a good day for sailing! Cached
Presence example: available 0 jabber:x:encrypted
Supports exchange of messages encrypted using the public key infrastructure (normally implemented using PGP by the client). A related namespace, jabber:x:signed, is used to support signed messages. Message example: This Message is Encrypted … PGP encrypted message…
97
98
All About Jabber Clients
Table 3.7 Continued Namespace jabber:x:oob
Use Indicates “out of band” data. Enables clients to exchange a standard URI with a description for the purpose of file transfers. URIs exchanged using jabber:x:oob can be included with any message (inside an subelement) and act as an attachment in the sense familiar from email. Multiple attachments can be included in one message. Message example: URL Attached. http://java.sun.com/javaone JavaOne Site
jabber:x:roster
Note: A client may choose to ignore OOB data, in which case nothing is seen at the client. This namespace allows a user to include roster items within a message, thus making it easy to send contact lists from one user to another. Each roster item is contained in an subelement within an element. Message example: My contacts! Exodus Friends
The Element
Using to Convey Arbitrary Data In its most familiar usage, Jabber facilitates the movement of short bits of text from one client to the next.What those textual bits mean in any true sense of the word is up to the interpretation of the humans or systems at the end points of the communication with mediation from the client software. From this perspective the Jabber server is “dumb” and clients may grow arbitrarily “smart.” From a practical standpoint, all that client end points have to ensure is that the XML stream they transmit conforms syntactically to the standards described thus far in the book. Message bodies can therefore contain pretty much any data the communicants want to put there.The only proviso is that the message body be contained within a tag and look like text.Therefore, you could, for example, embed Base64 data representing anything in a message body and have the client programs code and decode the data. To illustrate this point let’s look at an application that is an extreme use to suggest that the outer limits of intended usage are indeed pretty “outer.” For this exercise we are going to create an application that shares pictures between roster members.The application works like this: Anytime a client endpoint opens and displays a picture (GIF, JPEG, and so on) from the file system, that picture is transported to the endpoint’s roster members. For this exercise, we will assume all roster members are using the same client.The client code shown is a purpose-built client that handles only the intended functionality we have described. It is not a general Jabber textual chat client; as the code will show, it supports only examination of one’s roster, encoding of the binary data in an image, and transmission to the roster concurrent with file opening and display. We have a number of times said that a client need only support as much of the Jabber protocol elements as suit its needs, and this application which mixes in Jabber transport with a picture viewer application illustrates that point well. As a side effect, this example demonstrates that it’s relatively easy to add Jabber functionality to general applications. One of our disappointments with many technical books is that code examples are often contrived and don’t resemble the approach that you would actually take in coding a real application. Here we’ve taken a real Ruby language GUI application, stripped it down a bit so that it’s easier to present, and then added a few simple lines that turn it into a Jabber client. Note This example comes from the FOX Ruby site (http://fxruby.sourceforge.net) .FXRuby is a Ruby language extension module that provides an interface to the FOX GUI library. In imageviewer.rb you can see an application that is a model for a typical full-featured GUI application, with a menu bar, toolbar, and so forth.
Consider a group of clients whose relationship may be discerned from Figure 3.10.
99
100
All About Jabber Clients
Exodus - peer1... Exodus Tools
+ + bitmapper peer2 peer3
Help +
-
X
Exodus - peer3...
Jabber message switch
Exodus Tools
+ +
ON
-
X
Help +
bitmapper peer1
Off
Available
Available
Exodus - peer2... Exodus Tools
+ +
-
X
Help +
bitmapper peer1
Available
Figure 3.10 A group of clients.
Notice that peer1’s roster consists of peer2 and peer3. Peer2’s roster consists of peer1. Peer3’s roster consists of peer1 also. In this application, any graphic file that peer1 opens and displays will be transmitted to both peer2 and peer3, replacing whatever was on their local canvases. Any graphic file that peer2 opens and displays will be transmited to peer1, replacing whatever was on its local canvas. Likewise with peer3.Therefore, Figure 3.11 must have been generated by peer1 opening and displaying a picture of a rather wily looking coyote. How does this work? First, Listing 3.1 shows the Ruby code in its entirety, then deconstructs it.To run this code experimentally, you should install a current release of Ruby on your system, then add the jabber4r library.You also need to download the FOX Ruby library. Create some clients sharing rosters, as we showed in the previous figures. From the command line, launch an instance via ruby bitmapper.rb ‘account@host/resource’ ‘password’
The Element
Jabber message switch
ON Off
Figure 3.11 Binary messaging application in action.
Listing 3.1 Bitmapper.rb 1:#!/usr/bin/env ruby 2: 3: 4: 5:require ‘jabber4r/jabber4r’ 6: 7:(puts “bitmapper.rb ‘account@host/resource’ ‘password’”) ➥& exit if ARGV.size < 2 8: 9: 10:require ‘fox’ 11: 12:include Fox 13:
101
102
All About Jabber Clients
Listing 3.1
Continued
14:class ImageWindow < FXMainWindow 15: 16: attr_reader :jabber_session 17: 18: include Responder 19: 20: def initialize(app) 21: # Invoke base class initialize first 22: super(app, “Jabber Image Sender (#{ARGV[0]}): - untitled”, ➥nil, nil, DECOR_ALL, 23: 0, 0, 850, 600, 0, 0) 24: 25: # Make some icons 26: uplevelicon = getIcon(“tbuplevel.png”) 27: 28: # Status bar 29: statusbar = FXStatusbar.new(self, 30: LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|STATUSBAR_WITH_DRAGCORNER) 31: 32: # Splitter 33: splitter = FXSplitter.new(self, (LAYOUT_SIDE_TOP|LAYOUT_FILL_X| 34: LAYOUT_FILL_Y| ➥SPLITTER_TRACKING|SPLITTER_VERTICAL|SPLITTER_REVERSED)) 35: 36: # Sunken border for image widget 37: imagebox = FXHorizontalFrame.new(splitter, 38: FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y, 39: 0, 0, 0, 0, 0, 0, 0, 0) 40: 41: # Make image widget 42: @imageview = FXImageView.new(imagebox, nil, nil, 0, 43: LAYOUT_FILL_X|LAYOUT_FILL_Y) 44: 45: # Sunken border for file list 46: @filebox = FXHorizontalFrame.new(splitter, LAYOUT_FILL_X|LAYOUT_FILL_Y, 47: 0, 0, 0, 0, 0, 0, 0, 0) 48: 49: # Make file list 50: fileframe = FXHorizontalFrame.new(@filebox, 51: FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y, 52: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 53: @filelist = FXFileList.new(fileframe, nil, 0, 54: LAYOUT_FILL_X|LAYOUT_FILL_Y|ICONLIST_MINI_ICONS|ICONLIST_AUTOSIZE) 55: @filelist.connect(SEL_DOUBLECLICKED, method(:onCmdFileList)) 56: FXButton.new(@filebox, “\tUp one level\tGo up to higher directory.”,
The Element
Listing 3.1
Continued
57: uplevelicon, @filelist, FXFileList::ID_DIRECTORY_UP, 58: BUTTON_TOOLBAR|FRAME_RAISED|LAYOUT_FILL_Y) 59: 60: # Initialize file name and pattern for file dialog 61: @filename = “untitled” 62: @preferredFileFilter = 0 63: end 64: 65: # Convenience function to construct a PNG icon 66: def getIcon(filename) 67: FXPNGIcon.new(getApp(), File.open(filename, “rb”).read) 68: end 69: 70: def hasExtension(filename, ext) 71: File.basename(filename, ext) != File.basename(filename) 72: end 73: 74: def loadImage(file) 75: file = file.gsub( /\\/ , “/” ) # if you’re on WIN32.. 76: sendJabberImage(file) 77: updateImage(file) 78: end 79: 80: # Load the named image file 81: def updateImage(file) 82: img = nil 83: if hasExtension(file, “.gif”) 84: img = FXGIFImage.new(getApp(), nil, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) 85: elsif hasExtension(file, “.bmp”) 86: img = FXBMPImage.new(getApp(), nil, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) 87: elsif hasExtension(file, “.xpm”) 88: img = FXXPMImage.new(getApp(), nil, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) 89: elsif hasExtension(file, “.png”) 90: img = FXPNGImage.new(getApp(), nil, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) 91: elsif hasExtension(file, “.jpg”) 92: img = FXJPGImage.new(getApp(), nil, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) 93: elsif hasExtension(file, “.pcx”) 94: img = FXPCXImage.new(getApp(), nil, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) 95: elsif hasExtension(file, “.tif”) 96: img = FXTIFImage.new(getApp(), nil, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) 97: elsif hasExtension(file, “.tga”) 98: img = FXTGAImage.new(getApp(), nil, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) 99: elsif hasExtension(file, “.ico”) 100: img = FXICOImage.new(getApp(), nil, IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP) 101: end
103
104
All About Jabber Clients
Listing 3.1
Continued
102: 103: # Perhaps failed? 104: if !img 105: FXMessageBox.error(self, MBOX_OK, “Error loading image”, 106: “Unsupported image type: #{file}”) 107: return 108: end 109: 110: # Load it... 111: getApp().beginWaitCursor do 112: FXFileStream.open(file, FXStreamLoad) { ➥|stream| img.loadPixels(stream) } 113: img.create 114: @imageview.image = img 115: end 116: end 117: 118: # Quit the application 119: def onCmdQuit(sender, sel, ptr) 120: # Quit 121: getApp().exit(0) 122: end 123: 124: # Command message from the file list 125: def onCmdFileList(sender, sel, index) 126: if index >= 0 127: if @filelist.isItemDirectory(index) 128: @filelist.directory = @filelist.getItemPathname(index) 129: elsif @filelist.isItemFile(index) 130: @filename = @filelist.getItemPathname(index) 131: loadImage(@filename) 132: end 133: end 134: return 1 135: end 136: # Create and show window 137: def create 138: puts “creating app” 139: dir = “.” 140: @filebox.height = 100 141: super # i.e. FXMainWindow::create() 142: show(PLACEMENT_SCREEN) 143: end 144: 145:
The Element
Listing 3.1
Continued
146: 147: def connectToJabber(account, account_password) 148: begin 149: @jabber_session = Jabber::Session.bind_ ➥digest(“#{account}”, “#{account_password}”) 150: @jabber_session.add_message_listener do |message| 151: File.open(“TMP”+message.subject, “wb”) do |file| 152: file.write message.body.unpack(“m”)[0] 153: file.flush 154: end 155: updateImage(“TMP”+message.subject) 156: File.delete(“TMP”+message.subject) 157: 158: end 159: rescue 160: puts “Account does not exist: #{account} - #{account_password}” 161: puts $! 162: puts $!.backtrace 163: exit 164: end 165: end 166: 167: def sendJabberImage(imagefile) 168: puts imagefile 169: @jabber_session.roster.each_item do |item| 170: puts item 171: item.each_resource do |resource| 172: if resource.name == “bitmapper” 173: data = nil 174: File.open(imagefile, “rb”) {|file| data = file.read} 175: data = [data].pack(“m”) 176: @jabber_session.new_chat_message(item.jid.to_s+”/bitmapper”) .set_subject(File.basename(imagefile)).set_body(data).send 177: end 178: end 179: end 180: end 181: 182: def disconnectFromJabber 183: @jabber_session.close if @jabber_session 184: end 185:end 186: 187:if $0==__FILE__ 188: # Make application
105
106
All About Jabber Clients
Listing 3.1
Continued
189: application = FXApp.new(“ImageViewer”, “FoxTest”) 190: # Make window 191: window = ImageWindow.new(application) 192: 193: # Handle interrupts to terminate program gracefully 194: application.addSignal(“SIGINT”, window.method(:onCmdQuit)) 195: 196: # Create it 197: application.create 198: 199: # Connect to Jabber 200: window.connectToJabber(*ARGV) 201: 202: # Run 203: application.run 204: 205: # Shutdown Jabber connection 206: window.disconnectFromJabber 207: 208:end
Let’s begin looking at this example from back to front. First, a block (lines 187-208) outside the ImageWindow class definition says that if we are running this application from the command line (if $0==__FILE__ .. end), create an instance of ImageWindow into the local variable window (lines 189-191).We map an interrupt handler to a method (onCmdQuit) within the instance (line 194). Finally, we give the window its own thread of execution (line 203) and clean up the Jabber connection as the application exits. 187:if $0==__FILE__ 188: # Make application 189: application = FXApp.new(“ImageViewer”, “FoxTest”) 190: # Make window 191: window = ImageWindow.new(application) 192: 193: # Handle interrupts to terminate program gracefully 194: application.addSignal(“SIGINT”, window.method(:onCmdQuit)) 195: 196: # Create it 197: application.create 198: 199: # Connect to Jabber 200: window.connectToJabber(*ARGV) 201: 202: # Run 203: application.run 204:
The Element
205: # Shutdown Jabber connection 206: window.disconnectFromJabber 207: 208:end
During the stream of execution, we import the jabber4r library and check the number of command-line arguments. Execution then passes to the block above. 1:#!/usr/bin/env ruby 2: 3: 4: 5:require ‘jabber4r/jabber4r’ 6: 7:(puts “bitmapper.rb ‘account@host/resource’ ‘password’”) & exit if ARGV.size < 2 8: 9:
All of the code associated with the FXRuby extension is provided by the FOX module, so we need to start by requiring this feature: 10:require ‘fox’ 11:
Because all the FOX Ruby classes are defined under the FOX module, you normally refer to them by their “fully qualified” names (that is, names that begin with the Fox:: prefix).To avoid extra finger typing, you add an include Fox statement so that all the names in the FOX module are “included” into the global namespace: 12:include Fox 13:
The ImageWindow class inherits from a FOX MainWindow (line 14); standard practice with this toolkit is to subclass your own main window from FOX’s MainWindow class and construct its contents in the class’s initialize method (lines 20-63; see http:// fxruby.sourceforge.net for toolkit details). 14:class ImageWindow < FXMainWindow 15: 16: attr_reader :jabber_session 17: 18: include Responder 19: 20 .. 63
When we load the image for display, we also invoke sendJabberImage(file): 74: def loadImage(file) 75: file = file.gsub( /\\/ , “/” ) # if you’re on WIN32.. 76: sendJabberImage(file) 77: updateImage(file) 78: end
107
108
All About Jabber Clients
Let’s look at that in more detail.We get the current session’s roster and iterate over each roster entry.This is not cached in the jabber_session object, but rather fetched from the server each time to assure currency. (Technically there is some potential from a client to disconnect during this loop, but in this case, the server will simply drop the message.) The roster contains a dictionary of roster items, and each iteration yields one of these, which includes a Jabber ID (Jabber::JID) and a Jabber::Roster:: RosterItem::Resource.We check the resources to assure that the client is capable of receiving and displaying images (that is, it must have a resource called bitmapper, line 172). 167: def sendJabberImage(imagefile) 169: @jabber_session.roster.each_item do |item| 170: puts item 171: item.each_resource do |resource| 172: if resource.name == “bitmapper” 173: data = nil 174: File.open(imagefile, “rb”) {|file| data = file.read} 175: data = [data].pack(“m”) 176: @jabber_session.new_chat_message(item.jid.to_s+”/bitmapper”) .set_subject(File.basename(imagefile)).set_body(data).send 177: end 178: end 179: end 180: end
We create a Base64-encoded version of the file (line 175), and send it via the jabobject.We set the body of the message to the Base64 data (line 176) and send it.The construction of the message, the setting of a subject, and the message send are all chained in this terse call. Note that even though we send a chat-typed message, we set the subject to a filename. Setting subject in a chat message is unusual, but as we have pointed out previously, it is not forbidden by the protocol.You will see next how this becomes useful at the receiving instance. On receipt of the message, an instance fires its connectToJabber method. It pulls a suggested filename from the message subject, stores the decoded Base64 data into the file (line 152), then forces the FOX application to read the file and construct and display the image (line 155).
ber_session
147: def connectToJabber(account, account_password) 148: begin 149: @jabber_session = Jabber::Session.bind_digest(“#{account}”, “#{account_password}”) 150: @jabber_session.add_message_listener do |message| 151: File.open(“TMP”+message.subject, “wb”) do |file| 152: file.write message.body.unpack(“m”)[0] 153: file.flush
The Element
154: 155: 156: 157: 158:
end updateImage(“TMP”+message.subject) File.delete(“TMP”+message.subject) end
We catch exceptions in lines 159-165: 159: rescue 160: puts “Account does not exist: #{account} - #{account_password}” 161: puts $! 162: puts $!.backtrace 163: exit 164: end 165: end 166:
As a part of application cleanup (on exit), we disconnect the session, if one exists (line 183). 181: 182: def disconnectFromJabber 183: @jabber_session.close if @jabber_session 184: end 185:end 186:
This example uses the very complete jabber4r library, and as you can see, it offers a number of excellent high-level capabilities, including its own thread handling. We show this example in Ruby because of the language’s elegance and succinctness, but it serves to instruct for any other language implementation as well. If you prefer a Python example, here’s the similar application that uses PythonCard, a very nice GUI builder (http://pythoncard.sourceforge.net), one of several GUIs for Python. It looks slightly different—there are no command-line parameters and there is a separate login screen.We show all source for the three source files (pictureViewer.py, jabberLogin.py, and jabberHandler.py) composing the application for reference in Listings 3.2, 3.3, and 3.4 respectively; however, our comments apply mainly to the JabberHandler. Note To exercise this example, you need to download both the PythonCard application framework (from http://pythoncard.sourceforge.net) and the wxPython library (from http://www. wxpython.org). Just use the setup.py with each download to install them in the Python library folder.
109
110
All About Jabber Clients
Figure 3.12 PythonCard ImageViewer implementation.
Listing 3.2 pictureViewer.py 1:import sys, re, os, string 2:from PythonCardPrototype import clipboard, dialog, ➥graphic, log, model, EXIF 3:from wxPython import wx 4:import os, sys 5:import jabberLogin 6:from jabberHandler import JabberHandler 7: 8:class PictureViewer(model.Background): 9: 10: def on_openBackground(self, event): 11: self.ignoreSizeEvent = 1 12: 13: self.x = 0 14: self.y = 0 15: self.filename = None 16: self.bmp = None 17: 18: bgSize = self.getSize() 19: bufSize = self.GetClientSize() 20: widthDiff = bgSize[0] - bufSize[0] 21: heightDiff = bgSize[1] - bufSize[1] 22: displayRect = wx.wxGetClientDisplayRect() 23: self.maximizePosition = (displayRect[0], displayRect[1]) 24: self.maximumSize = (displayRect[2] - widthDiff, ➥displayRect[3] - heightDiff) 25: 26: if len(sys.argv) > 1: 27: # accept a file argument on the command-line 28: filename = os.path.abspath(sys.argv[1]) 29: log.info(‘pictureViewer filename: ‘ + filename)
The Element
Listing 3.2 30: 31:
Continued
if not os.path.exists(filename): filename = os.path.abspath(os.path.join(self.stack.app. ➥startingDirectory, sys.argv[1])) 32: if os.path.isfile(filename): 33: self.openFile(filename) 34: 35: if self.filename is None: 36: self.fitWindow() 37: self.jabberHandler = None 38: 39: self.Show(1) 40: 41: 42: def on_idle(self, event): 43: self.ignoreSizeEvent = 0 44: if self.jabberHandler is not None ➥and self.jabberHandler.isConnected(): 45: self.jabberHandler.Process() 46: def on_size(self, event): 47: if self.bmp is not None and not self.ignoreSizeEvent: 48: oldSize = self.bmp.getSize() 49: newSize = self.GetClientSize() 50: widthScale = newSize[0] / (0.0 + oldSize[0]) 51: heightScale = newSize[1] /(0.0 + oldSize[1]) 52: self.displayFileScaled(widthScale, heightScale, 1) 53: def sizeScaled(self, size, widthScale, heightScale): 54: return ((int(size[0] * widthScale), int(size[1] * heightScale))) 55: def displayFileScaled(self, widthScale, heightScale, inUserResize=0): 56: if self.filename is not None: 57: bufOff = self.components.bufOff 58: bufOff.autoRefresh = 0 59: # figure out new size for window 60: size = self.bmp.getSize() 61: newSize = self.sizeScaled(size, widthScale, heightScale) 62: bufOff.size = newSize 63: 64: if inUserResize: 65: self.panel.SetSize(newSize) 66: else: 67: self.fitWindow() 68: bufOff.clear() 69: 70: bufOff.autoRefresh = 1 71: bufOff.drawBitmapScaled(self.bmp, 0, 0, newSize)
111
112
All About Jabber Clients
Listing 3.2 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116:
Continued
# attempt to display the file full size if possible # otherwise the bitmap needs to be scaled def displayFile(self): if self.filename is not None: bufOff = self.components.bufOff bufOff.autoRefresh = 0 # figure out new size for window bufOff.size = self.bmp.getSize() self.fitWindow() bufOff.clear() bufOff.autoRefresh = 1 bufOff.drawBitmap(self.bmp, 0, 0) def fitToScreen(self): oldSize = self.bmp.getSize() newSize = self.maximumSize widthScale = newSize[0] / (0.0 + oldSize[0]) heightScale = newSize[1] /(0.0 + oldSize[1]) scale = min(widthScale, heightScale) self.displayFileScaled(scale, scale) self.Center() def fitWindow(self): self.ignoreSizeEvent = 1 size = self.components.bufOff.size self.panel.SetSize(size) #if self.ignoreSizeEvent == 1: self.SetClientSize(size) def openFile(self, path): self.filename = path f = open(path, ‘rb’) tags=EXIF.process_file(f) f.close() try: # the repr() is something like # (0x0112) Short=8 @ 54 # but the str() is just 1, 8, etc. orientation = int(str(tags[‘Image Orientation’])) except: orientation = 1 self.bmp = graphic.Bitmap(self.filename) if orientation == 8: # need to rotate the image # defaults to clockwise, 0 means counter-clockwise #print “rotating”
The Element
Listing 3.2
Continued
117: self.bmp.rotate90(0) 118: elif orientation == 6: 119: self.bmp.rotate90(1) 120: size = self.bmp.getSize() 121: title = os.path.split(self.filename)[-1] + “ %d x %d” % size 122: self.SetTitle(title) 123: # if either dimension of the image is beyond our maximum 124: # then display the image fit to the screen 125: if size[0] > self.maximumSize[0] or size[1] > self.maximumSize[1]: 126: self.fitToScreen() 127: else: 128: self.displayFile() 129: def on_menuFileOpen_select(self, event): 130: result = dialog.openFileDialog() 131: if result[‘accepted’]: 132: self.openFile(result[‘paths’][0]) 133: if self.jabberHandler is not None and ➥self.jabberHandler.isConnected(): 134: # distribute the picture to all clients on my roster 135: self.jabberHandler.sendToRoster(result[‘paths’][0]) 136: def on_menuFileConnectJabber_select(self, event): 137: result = jabberLogin.jabberLogin(self) 138: self.jabberHandler = JabberHandler(self, ➥jid=result[‘JabberIdText’], password=result[‘PasswordText’], ➥ server=result[‘JabberServerText’]) 139: self.jabberHandler.ConnectToJabber() 140: def on_menuFileSaveAs_select(self, event): 141: if self.filename is None: 142: path = ‘’ 143: filename = ‘’ 144: else: 145: path, filename = os.path.split(self.filename) 146: wildcard = “All files (*.*)|*.*” 147: result = dialog.saveFileDialog(None, “Save As”, ➥path, filename, wildcard) 148: if result[‘accepted’]: 149: path = result[‘paths’][0] 150: fileType = graphic.bitmapType(path) 151: try: 152: bmp = self.components.bufOff.getBitmap() 153: bmp.SaveFile(path, fileType) 154: return 1 155: except: 156: return 0 157: else:
113
114
All About Jabber Clients
Listing 3.2
Continued
158: return 0 159: def on_menuFileExit_select(self, event): 160: self.Close() 161: 162:if __name__ == ‘__main__’: 163: # require JabberID, Password, Server 164: app = model.PythonCardApp(PictureViewer ) 165: app.MainLoop()
The pictureViewer.py file is a simple frame for displaying image files. From the command line, invoke it via > python pictureViewer.py, as shown in Listing 3.3. Listing 3.3 jabberLogin.py 1:from PythonCardPrototype import model, res 2:import os 3: 4:class JabberLogin(model.CustomDialog): 5: def __init__(self, parent): 6: model.CustomDialog.__init__(self, parent) 7: 8: 9: def jabberLogin(parent): 10: dlg = JabberLogin(parent) 11: dlg.showModal() 12: result = {‘accepted’:dlg.accepted()} 13: result[‘JabberIdText’] = dlg.components.JabberIdText.text 14: result[‘PasswordText’] = dlg.components.PasswordText.text 15: result[‘JabberServerText’] = dlg.components.JabberServerText.text 16: 17: dlg.destroy() 18: return result JabberLogin.py shown in Listing 3.3 is a simple dialog box for collecting the appropriate connection information from the user. It’s shown in Figure 3.12. PictureViewer, the parent frame, calls JabberLogin whenever the user selects Connect to Jabber from the File menu: (in pictureViewer.py) 136: def on_menuFileConnectJabber_select(self, event): 137: result = jabberLogin.jabberLogin(self) 138: self.jabberHandler = ➥JabberHandler(self, jid=result[‘JabberIdText’], ➥password=result[‘PasswordText’], server=result[‘JabberServerText’]) 139: self.jabberHandler.ConnectToJabber()
The Element
When the GUI user logs into a Jabber connection, the JabberHandler is called. It’s the class that implements and encapsulates all the Jabber details for PictureViewer (pictureViewer.py, lines 138–139, and lines 42–45.) Listing 3.4 shows it in its entirety and then we deconstruct its significant implementation in detail. Listing 3.4 jabberHandler.py 1:import jabber 2:import sys 3:import sha 4:import sys, re, os, string, base64 5: 6:class JabberHandler: 7: def __init__(self, parent, jid=None, password=None, server=’localhost’, resource=”pictureviewer”): 8: self.parent = parent 9: self.jid = jid 10: self.password = password 11: self.server = server 12: self.roster = None 13: self.resource = resource 14: self.ConnectToJabber() 15: self.counter = 0 16: 17: 18: def ConnectToJabber(self): 19: self.con = jabber.Client(host=self.server, debug=0, port=5222, ➥log=sys.stderr) 20: try: 21: self.con.connect() 22: self.connected = True 23: except IOError, e: 24: print “Couldn’t connect: %s” % e 25: sys.exit(0) 26: else: 27: print “\nConnected\n” 28: 29: self.con.setMessageHandler(self.messageCB) 30: self.con.setPresenceHandler(self.presenceCB) 31: self.con.setIqHandler(self.iqCB) 32: 33: if self.con.auth(self.jid,self.password,self.resource): 34: print “Logged in as %s to server %s” % ( self.jid,self.server) 35: else: 36: print “ERR -> “, self.con.lastErr, self.con.lastErrCode 37: sys.exit(1) 38: self.con.sendInitPresence()
115
116
All About Jabber Clients
Listing 3.4
Continued
39: self.roster = self.con.requestRoster() 40: summary = self.roster.getSummary() 41: print “\nRoster:\n” 42: for name in summary.keys(): 43: print “\tname—>”, name, 44: print self.roster.getOnline(name) 45: print “\n” 46: 47: jids = self.roster.getJIDs() 48: for jid in jids: 49: print “JID—>”, jid 50: print “\n” 51: print “raw =”, self.roster.getRaw() 52: 53: def isConnected(self): 54: return self.connected 55: def sendToRoster(self, path): 56: print “sending to roster”, path 57: ## get from file and encode... 58: f = open(path, ‘rb’) 59: theData = f.read() 60: base64Data = base64.encodestring(theData) 61: for jid in self.roster.getJIDs(): 62: print “send pict —> JID “, jid 63: msg = jabber.Message(jid,base64Data) 64: msg.setSubject(os.path.basename(path)) 65: self.con.send(msg) 66: 67: 68: 69: def iqCB(self, iq): 70: “””Called when an iq is recieved, we just let the library handle it at the ➥moment””” 71: print “iqCB”, str(iq) 72: 73: 74: def presenceCB(self, con, prs): 75: “””Called when a presence is received””” 76: print “\npresenceCB\n”, str(prs) 77: who = str(prs.getFrom()) 78: type = prs.getType() 79: if type == None: type = ‘available’ 80: 81: # subscription request: 82: # - accept their subscription
The Element
Listing 3.4 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126:
Continued
# - send request for subscription to their presence if type == ‘subscribe’: print “subscribe request from %s” % who con.send(jabber.Presence(to=who, type=’subscribed’)) con.send(jabber.Presence(to=who, type=’subscribe’)) # unsubscription request: # - accept their unsubscription # - send request for unsubscription to their presence elif type == ‘unsubscribe’: print “unsubscribe request from %s” % who self.con.send(jabber.Presence(to=who, type=’unsubscribed’)) self.con.send(jabber.Presence(to=who, type=’unsubscribe’)) elif type == ‘subscribed’: print “we are now subscribed to %s” % who elif type == ‘unsubscribed’: print “we are now unsubscribed to %s” % who elif type == ‘available’: print (“%s is available (%s / %s)” % \ (who, prs.getShow(), prs.getStatus())) elif type == ‘unavailable’: print (“%s is unavailable (%s / %s)” % \ (who, prs.getShow(), prs.getStatus()))
def messageCB(self, con, msg): #def messageCB(self, msg): print “\nmessageCB\n” ### theData = base64.decodestring(msg.getBody()) suggestedFileName = “TMP”+msg.getSubject() print suggestedFileName f = file(suggestedFileName, “wb”) f.write(theData) f.close() self.parent.openFile(suggestedFileName) ## delete the tmp file?? def Process(self): self.con.process(0)
117
118
All About Jabber Clients
Here we’ll deconstruct only the JabberHandler class.The strategy the main application employs is that whenever it has idle cycles, it checks to see whether it has a handle to the JabberHandler and the user has connected to Jabber (if self.jabberHandler is not None and self.jabberHandler.isConnected()). If so, then it calls the JabberHandler.Process() method: (in PictureViewr.py) 42: def on_idle(self, event): 43: self.ignoreSizeEvent = 0 44: if self.jabberHandler is not None and self.jabberHandler.isConnected(): 45: self.jabberHandler.Process()
This in turn invokes the process() method of the underlying jabber and xmlstream classes (in jabber.py).This way the application can check regularly for incoming messages without blocking UI. A much better way to do this is to spawn a separate thread of execution for the JabberHandler, and use a Python Message Queue for the halves of the application to communicate. For simplicity, we did not do this here, but you should be aware that design strategy for intra-process communication can often have a significant impact on perceived performance. Note There is a good discussion of this particular topic at http://pythoncard.sourceforge.net/timers-threads.html.
When the GUI user logs into a Jabber connection, this is the class that implements the Jabber handling. The application imports needed features from the Jabber library and a few others: 1:import 2:import 3:import 4:import 5:
jabber sys sha os, base64
The constructor needs the parent application’s handle (the GUI), a user ID, a server name, and a password.These were all garnered from the GUI and passed to the constructor. 6:class JabberHandler: 7: def __init__(self, parent, jid=None, password=None, server=’localhost’, resource=”pictureviewer”): 8: self.parent = parent 9: self.jid = jid 10: self.password = password 11: self.server = server 12: self.roster = None 13: self.resource = resource 14: self.ConnectToJabber() 15:
The Element
The attempt to connect with the server is implemented here: 16: def ConnectToJabber(self): 17: self.con = jabber.Client(host=self.server, debug=False, ➥port=5222,log=sys.stderr) 18: try: 19: self.con.connect() 20: self.connected = True 21: except IOError, e: 22: print “Couldn’t connect: %s” % e 23: sys.exit(0) 24:
We set handlers for the various callbacks, and use our own overrides for the default methods: 25: 26: 27: 28:
self.con.setMessageHandler(self.messageCB) self.con.setPresenceHandler(self.presenceCB) self.con.setIqHandler(self.iqCB)
If we fail, the application exists. A more mature application might do something such as raise an error dialog and give the user another chance to log in. 29: 30: 31: 32: 33: 34:
if not self.con.auth(self.jid,self.password,self.resource): print “ERR -> “, self.con.lastErr, self.con.lastErrCode sys.exit(1) self.con.sendInitPresence() self.roster = self.con.requestRoster() summary = self.roster.getSummary()
These lines show off the Jabber library capabilities and are not needed to run the application.When lines 36–41 are executed: 35:### didactic code, not needed for application 36: print “\nRoster:\n” 37: for name in summary.keys(): 38: print “\tname—>”, name, 39: print self.roster.getOnline(name) 40: print “\n” 41:
the returned result looks like: Roster: name—> Pix2@localhost offline
name—> Pix3@localhost offline
119
120
All About Jabber Clients
And this: 42: 43: 44: 45: 46:
jids = self.roster.getJIDs() for jid in jids: print “JID—>”, jid print “\n” print “raw =”, self.roster.getRaw()
produces: JID—> Pix2@localhost JID—> Pix3@localhost
as well as the raw roster, which is a Python dictionary of dictionaries. Each JabberId (Pix2@localhost, Pix3@localhost) keys a dictionary of the relevant roster items (status, availability, and so on). raw = { u’Pix2@localhost’: {‘status’: None, ‘name’: u’Pix2’, ‘groups’: [], ‘online’: ‘offline’, ‘ask’: None, ‘show’: None, ‘sub’: u’both’ }, u’Pix3@localhost’: {‘status’: None, ‘name’: u’Pix3’, ‘groups’: [u’Unfiled’], ‘online’: ‘offline’, ‘ask’: None, ‘show’: None, ‘sub’: u’both’ } }
The sendToRoster method gets a cached roster and then sends the image to roster members: 50: def sendToRoster(self, path): 51: print “sending to roster”, path 52: ## get from file and encode... 53: f = open(path, ‘rb’) 54: theData = f.read() 55: base64Data = base64.encodestring(theData) 56: for jid in self.roster.getJIDs(): 57:
The Element
58: 59: 60:
msg = jabber.Message(jid,base64Data) msg.setSubject(os.path.basename(path)) self.con.send(msg)
Why did we encode the binary bits (line 55 and also on line 175 of Listing 3.1 the earlier Ruby example), and then pass that to the message constructor? Well, remember that the data in the body of a message must conform to the rule for ordinary PCDATA.The only reasonable choice is to turn any alien format (alien from the standpoint of XML) into Base64 ASCII data. On receipt, the destination client will simply decode the Base64 data and use it to create a binary object. We implement a minimal presence handling here, just to keep the roster peers in sync. 69: def presenceCB(self, con, prs): 70: “””Called when a presence is recieved””” 71: print “\npresenceCB\n”, str(prs) 72: who = str(prs.getFrom()) 73: type = prs.getType() 74: if type == None: type = ‘available’ 75: 76: # subscription request: 77: # - accept their subscription 78: # - send request for subscription to their presence 79: if type == ‘subscribe’: 80: con.send(jabber.Presence(to=who, type=’subscribed’)) 81: con.send(jabber.Presence(to=who, type=’subscribe’)) 82: 83: # unsubscription request: 84: # - accept their unsubscription 85: # - send request for unsubscription to their presence 86: elif type == ‘unsubscribe’: 87: self.con.send(jabber.Presence(to=who, type=’unsubscribed’)) 88: self.con.send(jabber.Presence(to=who, type=’unsubscribe’)) 89:
We implement a message callback (messageCB) to decode the message body contents (line 92), grab the message subject, use it for a temporary filename (line 93), write the decoded contents of the message (lines 94–96), and notify a method in the parent that there is a new image for display.The parent has a method (self.parent.openFile) that does just this. 90: def messageCB(self, con, msg): 91: 92: theData = base64.decodestring(msg.getBody()) 93: suggestedFileName = “TMP”+msg.getSubject() 94: f = file(suggestedFileName, “wb”) 95: f.write(theData) 96: f.close() 97: self.parent.openFile(suggestedFileName)
121
122
All About Jabber Clients
Finally, whenever the parent process invokes its JabberHandler’s Process method (pictureViewer.py, lines 42–45), the underlying Jabber process() method fires. 98: 99: def Process(self): 100: self.con.process(0)
You should use the Pythocard resource editor to produce pictureViewer.rsrc.py and jabberLogin.rsrc.py files corresponding to both the pictureViewer.py and jabberLogin.py files. It would stray too far off topic to discuss this further. If you need additional help in this step (somewhat unlikely,) please email the authors. Remember before running either the Ruby or Python example to modify your jabber.xml configuration file to allow larger file transfers (see Chapter 2). A “real” application such as this should have to break large binary data into many small messages to better conform to the Jabber model. Sending too large a single message would both overtax the server and possibly even break a client. Remember that the Jabber message model prefers short “IM-style” messages. Additionally, the Jabber server prefers many clients exchanging short messages as well. Note Also note that the server doesn’t respond well to message flooding. Thus, even if you broke the message up into several pieces and tried to send them simultaneously, the server still might complain to your application for violating the karma settings.
Summary This chapter looked at a client-side view of the Jabber world. By now, if you’ve absorbed some of what we’ve talked about, you must be thinking that the Jabber server is rather a complex and capable structure.We’d say that you’re exactly right! In the next chapter we move to deconstructing the server from an architectural perspective before we go on in Chapter 5 to look at a few of the things you can build using the client, server, and message concepts embodied in Jabber.
4 Jabber Server Architecture
Architecture is the learned game, correct and magnificent, of forms assembled in the light. Le Corbusier
N
OW THAT YOU’VE SEEN THE PROTOCOLS involved in Jabber messaging, in this chapter we look at how the Jabber reference server implementation is put together and how the messages flow within and between servers.
NOTE The server that we will discuss and use for our examples is the jabberd server implemented in C. It is the reference implementation of a Jabber server, but it’s not the only one available. There is a commercial server available from Jabber, Inc., and you can find more open-source servers on the JabberStudio Web site at http://www.jabberstudio.org.
High-Level Architecture The Jabber server (jabberd) is made up of several components that interact through exchanging messages on an internal message bus.The downloaded default configuration of jabberd includes the components that are needed to get an instant messaging server up and running: Jabber Session Manager (JSM).The JSM manages the registration of new user accounts, authenticates users, and manages presence information. It is by far the largest, most complicated component shipped with jabberd. c2s (Client-to-Server). c2s handles connections between the server and its clients. Its main job is formatting and routing messages between clients and other components (almost always JSM in the case of simple instant messaging). n
n
124
Chapter 4
n
n
n
n
Jabber Server Architecture
s2s (Server-to-Server). s2s handles connections between the server and other servers.The protocol for s2s connections is slightly different than c2s connections and this component speaks that protocol. xdb (XML Database). xdb responds to messages to store and retrieve data. It’s the shared persistent storage mechanism for the Jabber server. Logger. Logger services receive messages from other components intended to track the server’s actions. Logged messages include things such as user logins and logouts, errors, and so on.The standard jabberd configuration includes two loggers: elogger for errors and rlogger for all other system events.
(DNS Service). dnsrv resolves server names to IP addresses. Server names are almost always DNS host names, so this is a straightforward function.
dnsrv
A couple common additional components are Jabber User Directory (JUD). The JUD component provides services that enable clients to publish their contact information and to query for other clients’ information. Conferencing (conf). The conferencing component allows clients to join groups in which messages sent to the group are delivered to all other clients that are members of that group.This is commonly called the “chat room” function, but it has uses outside instant messaging. n
n
Each component has a Jabber ID (JID) that distinguishes it from other components. These components exchange messages among themselves over a data bus that routes messages to the appropriate component based on the type of message and the destination JID. In addition to the , , and messages that are exchanged between clients and servers, server components also exchange three other types of messages: n
n
n
—These
messages contain server status information and are delivered to the log component. —These messages contain either queries or persistent data that is being stored to or retrieved from the persistent store.The type attribute is get for queries, set for stores, and result for query responses. —Route messages wrap other messages for delivery to other components. They are like envelopes in that they can be used to enclose a message for delivery to a different address.
Figure 4.1 shows the components of a typical Jabber server. Notice how the connections between the components are all mediated by the component bus.This design is key to the Jabber server’s flexibility and scalability. It allows components to be configured and replaced independently and even allows them to be distributed to multiple computers if necessary.
Messages and Sessions
Figure 4.1 The jabberd component bus.
The following sections examine in detail examples of how messages flow among components.
Messages and Sessions First, take the simple case of a client sending a message to another client on the same server while both clients are online. A client’s connection to the server is maintained by the c2s component, so this interaction starts with the client initiating the message by sending its message packet to the c2s component: 1) user1 → c2s ED52C9B4928820B2ABC834876ADF084D Greetings! Hello, user2.
This message contains just the addressing information as an attribute to the tag, and the message payload in the , , and elements. The c2s component could just send this message to user2, but it needs to make sure that user2 doesn’t filter messages from user1. Message filter information is managed by the JSM (known internally to the server by the service name sessions), so c2s wraps
125
126
Chapter 4
Jabber Server Architecture
the packet in a packet for internal delivery between components. The resulting packet looks like this: 2) c2s → sessions ED52C9B4928820B2ABC834876ADF084D Greetings! Hello, user2.
When the JSM receives this packet, it needs to fetch user2’s filter information. Like all persistent data, filters are stored by the xdb component.The JSM forms and sends an packet like this: 3) sessions → xdb
In this packet, the value of the type attribute is get, which identifies this as a query; the value of the to attribute is user2@my-jabber, which identifies the user whose information is being queried; and the value of the ns (name space) attribute is jabber:iq: filter, which identifies what information about that user is being requested.The xdb component looks up the filter settings for user2 and replies with a packet like this: 4) xdb → sessions How are you Fine, thanks.
From this packet, we can see that user2 has set up one filter to automatically reply to messages that have a subject that includes the phrase How are you.The JSM checks the current message against the filter and decides that it’s not applicable, so the message is to be delivered normally to user2.The JSM wraps the message back into a route packet for delivery to c2s like this: 5) sessions → c2s ED52C9B4928820B2ABC834876ADF084D
Messages and Sessions
Greetings! Hello, user2.
The
c2s
component unwraps the message and delivers it to user2 in a packet like this:
6) c2s → user2 ED52C9B4928820B2ABC834876ADF084D Greetings! Hello, user2.
So six messages are required to send a message from user1 to user2.The exchange involves the c2s, sessions, and xdb components.To reiterate, this interaction is shown in Figure 4.2. user1
sessions
c2s
1
xdb
user2
2 3
lookup filters
4 5 6
Figure 4.2 Simple (one-server) messaging.
Remote Messaging Now it’s time to examine a slightly more complicated example. In this example, user1 again sends a message to user2, but this time user2 is connected to a different server. User1 creates this message and sends it to the c2s component of its local server: 1) user1 → c2s (my-jabber) 05C2A7562274838DBA443AD698271C82 Greetings!
127
128
Chapter 4
Jabber Server Architecture
Hello, user2
As before, the c2s component wraps this message in a be delivered internally to the JSM:
packet and sends it to
2) c2s (my-jabber) → sessions (my-jabber) 05C2A7562274838DBA443AD698271C82 Greetings! Hello, user2
The JSM sees that this is a message for another server, so it unwraps it from the packet and sends this packet to be delivered internally to the s2s (server-toserver) component:
3) sessions (my-jabber) → s2s (my-jabber) 05C2A7562274838DBA443AD698271C82 Greetings! Hello, user2
We’ll assume that the two servers have already established a connection and authenticated each other with the dialback protocol (see Chapter 6 for details on dialback), so local s2s can just use that connection to send the message to the other s2s: 4) s2s (my-jabber) → s2s (other-server) 05C2A7562274838DBA443AD698271C82 Greetings! Hello, user2
This packet is received by the remote
s2s
and sent for routing to the remote JSM:
5) s2s (other-server) → sessions (other-server) 05C2A7562274838DBA443AD698271C82 Greetings! Hello, user2
All that remains at this point is to deliver the message locally, so it’s much like the first example.The JSM needs to run the message against user2’s filters, so it queries xdb:
Messages and Sessions
6) sessions (other-server) → xdb (other-server)
The
xdb
component looks up the user’s filters and responds with a packet like this:
7) xdb (other-server) → sessions (other-server)
In this case, user2 has no filters, so the JSM can just wrap the message in a packet and send it for delivery to c2s:
8) sessions (other-server) → c2s (other-server) 05C2A7562274838DBA443AD698271C82 Greetings! Hello, user2
The c2s component receives this route packet, unwraps the enclosed message, and sends it over the connection it maintains with user2: 9) c2s (other-server) → user2 05C2A7562274838DBA443AD698271C82 Greetings! Hello, user2
So it takes a few more messages, but remote delivery of a message is not too much more complicated than local delivery.The sequence diagram in Figure 4.3 outlines these steps. s2s (my-jabber)
user1
sessions (my-jabber)
s2s (my-jabber)
s2s (other-server)
sessions (other-server)
xdb (other-server)
c2s (other-server)
user2
1 2
3
4
5
lookup filters
6
7 8
Figure 4.3 Remote (server-to-server) messaging.
9
129
130
Chapter 4
Jabber Server Architecture
Client Initialization When a typical instant messaging client logs in to a server, it does several things to get itself initialized: 1. It establishes a connection to the server and identifies itself as a Jabber client. 2. It authenticates to the server (that is, it somehow proves to the server who it is). 3. It asks for the list of services (agents) available on that server. 4. It asks for its roster (which is stored on the server). 5. It sets its presence display for other clients to see. This section traces in detail the messages that are sent to make these things happen. Step 1: Connecting First, of course, the client connects to the c2s component.They exchange stream headers over that connection so the client can verify that it’s talking to a Jabber server and the c2s component can verify that it’s talking to a Jabber client.The exchanged messages look like this: 1) Client → c2s
The details aren’t too important, but the to attribute tells the server the name of the server to which the client thinks it’s talking (“my-jabber” in this case), and the xmlns attribute tells the server that this is a Jabber client: 2) c2s → Client
This stream header, sent from the c2s component to the client, acknowledges that the client is talking to a Jabber server, and that the server’s name is “my-jabber.”The id attribute provides an identifier for this connection that the client will need to use when it authenticates. Figure 4.4 shows the interactions between the client and c2s for this action. Step 2: Authenticating So far, all the messages have been between the c2s component and the client, but actually starting a session for the client involves several other components. It begins when the client requests the instructions for logging in (authenticating) with this IQ packet: 1) Client → c2s
Messages and Sessions
user1
Client
c2s 1 2
Figure 4.4 Connection interactions.
The c2s component doesn’t handle authentication, so it wraps this IQ message in packet, sets its type to auth (for “authentication”) and submits it to delivery any component that handles auth messages.This message gets delivered to the JSM component (known internally by the service name sessions):
a to
2) c2s → sessions user1
Notice how c2s uses the from attribute JID in the packet to hold information about the connection.This information will come back in the to attribute of the reply, enabling c2s to associate the reply with the original request.This is a common trick you’ll see used many places. When JSM (“sessions”) receives this message, it needs to get some information about user1@my-jabber to be able to reply to this message. It needs to query the XML database to get the login information required for this user, so JSM generates an message like this: 3) sessions → xdb
131
132
Chapter 4
Jabber Server Architecture
The type (get) identifies this as a query.The to and ns attributes are the parameters to the query and are used to route the request to the correct xdb instance, if there is more than one, and to identify what data will be retrieved.The JSM uses the id attribute to match the response that will follow with this query. The xdb component receives this request, looks in its database, and if all goes well, responds with a message like this: 4) xdb → sessions user1
This tells the JSM that user1’s password is user1 (not a very good idea, by the way). JSM also supports a form of authentication called zero-knowledge (see Chapter 6 for details), so it requests the zero-knowledge state information also: 5) sessions → xdb
Here the ns=’jabber:iq:auth:0k’ tells fetches it and replies with this message:
xdb
the record that JSM needs it to fetch.
xdb
6) xdb → sessions 3DE4F2A3 453 d1d7b5a13677af3f6669538bd9c89a8eee6de5d9
The record element contains three values: token, sequence, and hash, which the JSM uses to authenticate the client with zero-knowledge authentication. Now that it has everything it needs, JSM can respond to the message from c2s: 7) sessions → c2s user1 452 3DE4F2A3
Messages and Sessions
This message wraps an message that the c2s should send to the client so the client knows how to authenticate.The packet that c2s sends to the client looks like this: 8) c2s → Client user1 452 3DE4F2A3
Now the client knows what it needs to provide to log in to the server, so it generates a login packet like this and sends it to c2s: 9) Client → c2s user1 user1 Work
As before, c2s wraps this packet in a tion component (that is, the JSM):
packet for delivery to the authentica-
10) c2s → sessions user1 user1 Work
Again this auth message is routed to the JSM component.The JSM already has the password from before and can compare it with the one in this packet.They match, so it responds with a wrapped IQ success packet for the client—by way of c2s:
133
134
Chapter 4
Jabber Server Architecture
11) sessions → c2s c2s
unwraps the
packet and sends the success IQ packet to the client:
12) c2s → Client
At this point, the client is authenticated, but there is still some internal housekeeping to do.The c2s notifies the session manager that it should create a session with a packet like this: 13) c2s → sessions
If it successfully creates the session, the JSM replies: 14) sessions → c2s
Finally, c2s creates a log entry to make a record of the successful login. 15) c2s → log login ok 192.168.1.103 Work
Figure 4.5 shows these interactions between the client and server components when authenticating. Step 3: Querying for Agents At this point, the client is successfully logged in and can send messages or use any other functions of the server.The next thing an IM client may ask for is a list of agents so it can tell the user what services are available.The client starts the process by sending an IQ packet to c2s: 1) Client → c2s
The c2s component wraps that packet in a which maintains the list of browsable agents:
packet for delivery to the JSM,
2) c2s → sessions
Messages and Sessions
c2s
Client
sessions
xdb
log
1 2 3
lookup
4 5 lookup
6 7 8
9 10 11 12 13 14 15
Figure 4.5 Authentication interactions.
Notice again the contents of the from attribute in the element.The host field (c2s) ensures that a reply will be delivered to c2s; all the rest (the username and resource) is used by c2s to associate requests with replies. The JSM responds with a list of the agents available on the server in an IQ result packet wrapped in a packet for c2s: 3) sessions → c2s Global Mega Corp Directory jud Public Chatrooms public
135
136
Chapter 4
Jabber Server Architecture
c2s unwraps the packet and sends the IQ packet back to the client so it knows about the local JUD directory service and the Public Chatrooms conferencing service:
4) c2s → Client Global Mega Corp Directory jud Public Chatrooms public
Now the client could contact either of those services directly by using the JID provided in this reply. Figure 4.6 shows these interactions as a sequence diagram. c2s
Client
1
sessions
2
3
4
Figure 4.6 Querying for agents.
Step 4: Fetching the Roster A user’s roster is stored on the server, so when an IM client connects, one of the first things it does is fetch the roster.The IM client displays the roster for the user and uses it to organize presence information from the roster members. As you probably guessed, it starts with the client sending a packet to c2s:
Messages and Sessions
1) Client → c2s
The c2s component wraps this in a packet and sends it.The JSM handles these packets, so it gets delivered there: 2) c2s → sessions
The JSM stores users’ roster information in fetch it:
xdb, so
it generates an
message to
3) sessions → xdb xdb looks up the requested data (jabber:iq:roster) for the requested user (user1@myjabber), and replies with a packet like this:
4) xdb → sessions My Resources
This roster contains one contact (user2@other-server) in the contact group My Resources.The JSM puts this data in the appropriate IQ packet and wraps the IQ in packet for delivery to c2s: 5) sessions → c2s My Resources
a
137
138
Chapter 4
Jabber Server Architecture
c2s removes the IQ packet from the data to the client:
packet and sends the completed roster
6) c2s → Client My Resources
The sequence diagram for this interaction is shown in Figure 4.7. c2s
Client
1
sessions
2
xdb
3
lookup
4
5 6
Figure 4.7 Fetching the roster.
Step 5: Setting the Presence The last step in getting the IM client initialized is setting the user’s presence information.This tells other clients that the user is online and ready. Again the first message is from the client to c2s: 1) Client → c2s Online
This little packet gets wrapped in a
packet and delivered to the JSM:
2) c2s → sessions Online
Messages and Sessions
Now that the client is online, the JSM can send it any messages that have been stored for it while it was offline. Like all persistent data, these messages are stored in xdb, so the JSM sends an message like this: 3) sessions → xdb
This client has no offline messages, so element like this:
xdb
responds with a packet with an empty
4) xdb → sessions
At this point, the JSM tries to send any queued offline messages, if any exist. After they are sent, the xdb is updated to clear the old messages with a packet like this: 5) sessions → xdb
Notice that the type is set rather than get, to identify this as a database write rather than a read. If this is successful, xdb responds with a success result like this: 6) xdb → sessions
The change in the client’s presence status also means that the clients on its roster need to be notified.The JSM is responsible for this also, so it fetches the client’s roster from xdb: 7) sessions → xdb xdb
responds with the user’s roster as before:
8) xdb → sessions My Resources
139
140
Chapter 4
Jabber Server Architecture
For each subscriber to this client’s presence information, the JSM generates a presence packet.This client has only one presence subscriber, but it is on a different server (user2@other-server), so the packet generated is delivered to the s2s component for remote delivery: 9) sessions → s2s Online
The stamp attribute of the element is a timestamp to record the time that user1’s presence status changed.The local s2s component delivers this packet to the remote s2s component: 10) Local s2s → Remote s2s Online
As you can see, this presence packet is sent unmodified to the remote s2s component. Again, we assume that the two s2s components are already connected and don’t have to authenticate each other with the dialback protocol (described in Chapter 6). The local client (user1@my-jabber) also wants to know the presence of the other client (user2@other-server), so JSM sends a presence probe packet also: 11) sessions → s2s
This packet is sent unmodified by the local s2s component to the s2s component on the remote server: 12) Local s2s → Remote s2s
The remote s2s receives the packet, interacts with the remote JSM (not shown here), and responds with the presence status for user2@other-server:
Messages and Sessions
13) Remote s2s → Local s2s Online
The local s2s accepts this packet and queues it for delivery to the JSM: 14) s2s → sessions Online
The JSM wraps this presence packet in a component:
packet for delivery to the
c2s
15) sessions → c2s Online
Finally, c2s can unwrap the the client:
packet and deliver the presence information to
16) c2s → Client Online
The sequence diagram in Figure 4.8 shows these messages from the perspective of the local client. As you can see, presence turns out to be fairly complicated, requiring sixteen messages among the client and server services just for a single (bi-directional) presence subscription. Of course, this is not counting those messages internal to the remote server and between the remote server and its client.
141
142
Chapter 4
Jabber Server Architecture
Figure 4.8 Registering presence.
Browsable Agents In step 3 (Querying For Agents) in the previous section, you saw that the instant messaging client queried the server for a list of agents when it connected. As described in Chapter 2, this list is managed by the mod_agents module in the JSM.The list of browsable agents is configured in the section of jabber.xml where a typical configuration might look like this: jabber:iq:search jabber:iq:register
The first entry (starting at the tag) is for the Jabber User Directory.The JID here refers to a service defined by a section defined elsewhere in jabber.xml.The tags declare what XML namespaces the service understands. In this case, the jabber:iq:search tells clients that this service can be searched, and the jabber:iq:register namespace indicates that this service supports registration.The
Browsable Agents
corresponding service configuration appears separately in (sessions) service configuration:
jabber.xml, outside
the JSM
jud.my-jabber ./jud-0.4/jud.so Global Mega Corp Directory This service provides a simple user directory service. http://foo.bar/
Notice that the contents of the tag (jud.my-jabber here) have to match the JID in the section of the JSM configuration.This is how clients know how to contact services. The second entry in the configuration above declares a special kind of service: a conference, or chat room, service. Its configuration is similar to the JUD service, and also appears separately as a configuration: ./conference-0.4/conference.so conference.my-jabber Public Chatrooms This service is for public chatrooms. http://foo.bar/ 20 has become available has left is now known as
Here again, the contents of the tag (conference.my-jabber) match the jid attribute in the JSM’s configuration. You saw earlier how a client queries the list of agents from the JSM; now look at how a client interacts with a service after the JSM tells it the list of services. By listing the service in the section of the JSM configuration, you’re telling the client that this service responds to IQ messages in the jabber:iq:browse namespace.This means that the client can send a message to the JID for this service and get back information about it.To start the process, a client sends this IQ message to the JUD service:
143
144
Chapter 4
Jabber Server Architecture
The packet is routed to the et like this one:
jud.my-jabber
component, which responds with a pack-
jabber:iq:register jabber:iq:search
This is the same information that we put in the section of the JSM configuration.They don’t have to be the same, but you can save clients a step if the JSM information is accurate. This response says that this service supports registration using the jabber:iq: register namespace, so the client can register by first requesting the list of fields and then generating a packet that sets those fields. First step: Send an IQ packet to get the registration fields:
Notice that the type=”get” attribute identifies this as a request for the registration instructions and not an actual registration.This message is routed to the JUD component, which replies with this information: Complete the form to submit your searchable attributes in the Jabber User Directory
This reply tells the client two things:The contents of the element gives the client human-readable instructions for the registration, and the other elements inside the element list the fields that are understood for registration. In this case, those fields are name, first, last, nick, and email. An instant messaging client can
Browsable Agents
use this information to build a form or wizard to present to a user.The WinJab IM client makes a three-step wizard, as shown in Figure 4.9
Figure 4.9 JUD Registration Wizard.
Notice that the first panel in the wizard includes the text from the element in the IQ packet.The second panel has fields for each one of the other elements.When the user fills this in and submits it,WinJab sends this message: Full Name Full Name user1 user1@email
After it is inside the server, this packet is wrapped in a packet as you saw earlier and routed to the JUD component. JUD parses the contents and sends an message like this to store this information:
145
146
Chapter 4
Jabber Server Architecture
Full Name Full Name user1 user1@email xdb stores this data and replies back to JUD with the appropriate success packet.Then JUD can reply to the client (by way of c2s, of course) with a success packet of its own:
This lets the client know that its registration was accepted. Searching is a similar process. First, the client sends an IQ get packet by using the jabber:iq:search namespace:
This packet is routed to the JUD component, which responds with instructions and a list of fields that can be used to search for a record. It’s no surprise that these are the same fields used to register: Fill in a field to search for any matching Jabber User
WinJab uses this information to build the form shown in Figure 4.10. The instructions from the IQ response are at the top of the form and the fields are listed on the left side. If you fill in the form as shown, with the field set to Full, and click Search,WinJab sends this packet to JUD: Full
Browsable Agents
Figure 4.10 JUD search form.
It’s interesting that the type of this packet is set even though we’re not “setting” anything. In the case of services like this, it may be clearer to think of get as a request for instructions about how to use the service and set as an actual invocation of the service. The packet gets routed to JUD in the usual way.You might expect it to get turned into a query to xdb (we sure did) but it turns out that this implementation of JUD stores all its data in memory and does one big fetch from JUD when it is first used. So the JUD component searches through its registrations and returns any that match in a packet like this: Full Name Full Name user1 user1@email
The tag delimits separate entries in the JUD. In this case there is only one entry. We can think of the namespaces jabber:iq:search and jabber:iq:register as protocols that both the JUD service and the WinJab client recognize.The powerful thing about a protocol like this is that either end can be swapped out and the other end is none the wiser. Any client that speaks the protocols can communicate with any service that speaks the protocols.The next chapters show you how to leverage these protocols. Although these examples illustrate the use of the standard Jabber browsing, searching, and registering protocols, it’s also reasonable to define your own protocols by using your own XML namespaces to distinguish them from the Jabber protocols. In this way, you’re using the Jabber server architecture as a message routing processor between your own services and clients.The next chapter gives you examples of this, also.
147
148
Chapter 4
Jabber Server Architecture
Instant Messaging Gateways The original motivation for Jabber was to build an extensible architecture that could be used to bridge between disparate instant messaging services. As you would expect, these bridges are implemented as Jabber services. At the time of this writing, several gateway transports are available on http://www.jabberstudio.org, including gateways for Yahoo Messenger, AOL Instant Messenger (AIM), Internet Relay Chat (IRC), and Microsoft’s MSN Messenger.They’re all similar in that they are Jabber services that know how to translate between the Jabber protocols and the “foreign” IM system.They’re similar in principle, so in this section, we look closely at one of them: the AIM transport. After downloading and building the AIM transport according to its instructions, you need to configure the Jabber server to load the AIM transport service.You do that by adding a section like this to the services section of jabber.xml: ./aim-transport/src/aimtrans.so /usr/local/AIM AIM Transport An AIM Transport! http://foo.bar/
The section tells the server that this will be a component loaded into the server directly from the shared library listed here.The services configuration is in the element and includes the odd entry .The AIM transport service uses information in AOL’s installation directory to talk to the server, so it requires an installation of the AOL software.The last thing is the vCard, which just gives some information about this service. You also want clients to be able to browse to this service, so you need to put an entry in the section of the JSM’s configuration. Something like this jabber:iq:gateway jabber:iq:register
This entry tells clients that the service at JID, “AIM (AOL Instant Messenger). my-jabber”, is a gateway to another system (jabber:iq:gateway) and it supports registration (jabber:iq:register), just as you saw earlier with the JUD component. After these are added to jabber.xml and jabberd is restarted, the new service appears to clients that query for agents, as in the WinJab browser shown in Figure 4.11.
Instant Messaging Gateways
Figure 4.11 Browsing the AIM Transport.
If you click on the AIM Transport icon,WinJab sends a browse IQ message to the AIM transport service:
As with the services you saw earlier, the AIM transport responds with its supported namespaces: jabber:iq:register jabber:iq:gateway
The elements contain the namespaces that the AIM transport understands. Let’s register the AIM user with the gateway so it can translate for you. As you saw already, the client can ask for the registration instructions with an IQ get packet like this:
This packet gets routed to the AIM transport, which replies with its registration instructions:
149
150
Chapter 4
Jabber Server Architecture
18b4718ea1ecb8fbff04a28cce188fb9d54760da Enter your AIM screenname or ICQ UIN and the password for that account
The gateway needs to know the AIM screenname and password (both user1-aim in the following example packet) so it can log in to the AOL system.The client passes the element back to the service when it does its registration: user1-aim user1-aim 18b4718ea1ecb8fbff04a28cce188fb9d54760da
If it succeeds in registering the AIM user, the gateway replies back with a success packet like this:
Now the service can relay messages between the AIM system and the Jabber server. Behind the scenes, the AIM transport has stored the registration information in xdb so it can connect to the AIM system on your behalf.The gateway also needs to be able to synchronize presence status on both systems, so it subscribes to user1’s presence by sending a presence packet like this:
Finally, the service advertises its own presence by sending its status in a presence packet: Connected
Now that user1 is registered with the AIM transport, he can send a message to an AIM user called user2 by addressing it to the Jabber address
[email protected] host specification aim.my-jabber in the address causes this message to get routed inside the Jabber server to the AIM gateway, which extracts the user name user2 and treats it as the AIM screenname. An AIM user can send user1 a message, too. Remember that the screenname user1aim was used for registration with the AIM transport, so the gateway will pick up any AIM messages for user1-aim and route them through the Jabber server to user1@my-jabber.
Instant Messaging Gateways
Figure 4.12 shows how the gateway translates between Jabber and AIM addresses.The Jabber client (user1@my-jabber) addresses its message to the AIM user (screenname user2-aim) as
[email protected] Jabber gateway takes the Jabber username and translates that to an AIM screenname and passes the message to AIM.
Jabber Client (user1@my-jabber)
AIM Client (user2-aim)
To:
[email protected] To: user2-aim
AOL Server
Jabber Server
AI Ga t M ewa y To: user2-aim
Internet
Figure 4.12 Sending messages from Jabber to AIM.
Figure 4.13 shows the reverse. Here an AIM user (screenname user2-aim) addresses a message to user1 by sending it to the screenname that user1 used when registering with the AIM gateway (user1-aim).The AIM system delivers this message to the AIM gateway, which converts it to a Jabber message addressed to user1@my-jabber for delivery as usual. Addressing is probably the simplest example of the translations that a gateway has to perform.The gateway is also responsible for translating presence information between systems, as well as other features that may or may not be supported by the Jabber protocols. Because of these mismatches, most gateways support only a subset of the features available on foreign IM systems. For the example illustrated in Figure 4.13,, the server to which the client was connected was the same one in which the gateway was installed.This is not a requirement at all. A client can register and use a service on any server that allows it access. Figure 4.14 shows how this would work.
151
152
Chapter 4
Jabber Server Architecture
Jabber Client (user1@my-jabber)
AIM Client (user2-aim)
To: user1@my-jabber To: user1-aim
AOL Server
Jabber Server
AI Ga t M ewa y
Internet
To: user1-aim
Figure 4.13 Sending messages from AIM to Jabber.
Jabber Client (user1@server1)
AIM Client (user2-aim)
To:
[email protected] To: user2-aim
Jabber (server1)
AOL Server
Jabber (server2)
AI Gat M ewa y
nt
t
Com s2s pon e
Com s2s pon en
Internet
To: user2-aim
Internet
To:
[email protected]
Figure 4.14 Sending messages through a remote gateway.
Summary
In this scenario, user1@server1 has registered with the AIM gateway on server2 (JID aim.server2). User1 can send its AIM message just as before and the s2s component on server1 routes it to server2, where it is routed to the AIM transport, which forwards it to the AIM system. As you can see, it’s possible to set up a pretty elaborate set of forwarding agents.
Summary In this chapter, you’ve seen how messages flow among components in a typical Jabber server and also how Jabber servers interact with one another.You saw some common instant messaging components, including the Jabber User Directory, the conferencing component, and gateways.The next chapter expands on these concepts to show you how you can use Jabber for applications other than instant messaging to do some incredibly flexible and scalable system-to-system integration.
153
5 Extending the Jabber Server
In the service of Caesar, everything is legitimate. Pierre Corneille (1606–1684), French playwright
All right, brain.You don’t like me and I don’t like you, but let’s just do this and I can get back to killing you with beer. Homer Simpson
T
HIS CHAPTER TAKES THE CONCEPTS THAT WERE covered somewhat abstractly in the previous chapters and makes them concrete with some extended examples.You’ll see how to use the components of the standard Jabber server in new ways and build components that give you capabilities beyond instant messaging. Indeed, you will see that just about anything is legitimate in the realm of Jabber services. As in previous chapters, this chapter is structured as a series of examples that illustrate the important concepts of building distributed applications with Jabber. In particular, the concepts we’ll discuss include the following: Configuring the Jabber server to load a custom service Configuring the Jabber Session Manager (JSM) to advertise the capabilities of a custom service Writing code to handle browse requests from clients Using the jabber:iq:search protocol to handle searching Using the jabber:iq:register protocol to register clients n
n
n
n
n
156
Extending the Jabber Server
n
n
n
n
n
n
n
Building interfaces between Jabber services and external systems including relational databases Using instant messaging clients to debug custom services Sending error responses to clients Composing services (that is, services that call other services) into distributed applications Using the logging service to report status and errors Defining new XML namespace types and storing them in XDB Interacting with Component Services
Although these topics are not exhaustive, by the end of this chapter, you’ll have a firm grasp of what’s required to build a capable custom Jabber service and have a large body of code to draw from when you start to write your own service.
A Database Service The first example shows how to use a Jabber service to make a relational database available as a Jabber resource. Jabber concepts don’t map directly into the concepts of a relational database, but there are some areas where they overlap: Searching. The Jabber protocols support searching with the jabber:iq:search namespace. Databases support searching with SQL. One can build a Jabber service to translate between these two languages so that a client can use the Jabber protocol to search the database. Browsing. The Jabber JSM service supports browsing with the jabber:iq: browse namespace. One can build a Jabber service using the Jabber protocols that enable a user to explore the database using an instant messaging client. n
n
This example is in Java and uses the JabberBeans library to communicate with the server. It communicates to the database by using the Java JDBC libraries, which are part of the standard distribution of the Java runtime environment.We use the MySQL database for this example, but this service should be easily portable to any database with a JDBC driver. Naturally, this Jabber service won’t make all the capabilities of SQL available to Jabber clients, but it will show how any capabilities you might need could be realized as Jabber services.
Configuring the Service TIP This service example assumes that you have installed and configured a MySQL database server and that you have the Java connector for MySQL (called MySQL Connector/J) available. Instructions for downloading and installing MySQL and the MySQL Java connector are at http://www.mysql.com/downloads/.
A Database Service
This service is built as an “accept” service, so it communicates with the server over a TCP/IP socket, which means that it doesn’t have to run on the same computer as the Jabber server.This is probably the most flexible of the methods of connecting a Jabber service in typical cases. Of the others, load requires the service to run on the same computer as the Jabber server itself, limiting scalability and flexibility.The connect method requires the service to accept the connection from the server, so it assumes that the service is always available—not as good for our purposes. In many cases where this service might be appropriate, the database might be behind a firewall while the Jabber server is outside the firewall so it can be publicly available.This configuration is shown in Figure 5.1.
Figure 5.1 A typical configuration of the database service.
In this configuration, because the database service makes the connection to the server from inside the firewall, there’s no need to open any incoming ports on the firewall, so there’s no firewall administration required and no additional security risks. Here’s the stanza of XML that configures this service as an accept service in the jabber.xml file: 9001 secret
This says that the service will be called (its JID will be) dbservice.my-jabber (the server’s name is my-jabber). It will connect to the server from an unknown IP address () on port 9001, using the password secret to prove its identity.
157
158
Extending the Jabber Server
That’s all that is required for the service to be accepted and handled by the Jabber server, but we also want the service to be available to clients for browsing and searching using the jabber:iq:browse and jabber:iq:search namespaces, so we need to configure the JSM to advertise the service.This is the stanza that, when added to the section of the JSM’s configuration, enables clients to discover the service: jabber:iq:search jabber:iq:browse
This says that the service named Database Service is available at the JID and that it responds to Jabber search and browse requests. After these two stanzas are added to jabber.xml, the server is ready for the service to connect, and the JSM will advertise the service to browsing clients such as the instant messaging client in Figure 5.2. dbservice.my-jabber
Figure 5.2 Browsing Jabber services.
Code Listing Clicking on the Database Service icon in Figure 5.2 causes the IM client to send a browse message to the service, so it’s time to look at the code.The JabberBeans library is a fairly low-level library, meaning that just about every element in an XML message requires a line of Java code, so this is a lengthy piece of code. Listing 5.1 provides it all here; then we’ll go through it piece by piece. Listing 5.1 The Database Service 1:package jdh.demos; 2:import java.net.*; 3:import java.io.*;
A Database Service
Listing 5.1 Continued 4:import 5:import 6:import 7:import 8:import 9:import
java.util.*; java.sql.*; org.jabber.jabberbeans.*; org.jabber.jabberbeans.Extension.*; org.jabber.jabberbeans.serverside.*; org.jabber.jabberbeans.util.*;
10:public class DBService implements PacketListener { 11: ConnectionBean cb; 12: public DBService(String[] args) { 13: String server_host = args[0]; 14: int port = Integer.parseInt(args[1]); 15: String secret = args[2]; 16: cb = new ConnectionBean(); 17: try { 18: cb.disableStreamHeader(); 19: cb.connect(InetAddress.getByName(server_host), port); 20: setup(server_host, secret); 21: } catch (Exception ex) { 22: ex.printStackTrace(); 23: } 24: } 25: // Initialize the connection to the jabber server. 26: private void setup(String server_host, String secret) throws Exception { 27: // load the JDBC driver 28: Class.forName(“org.gjt.mm.mysql.Driver”); 29: XMLStreamHeaderBuilder xsbuilder = new XMLStreamHeaderBuilder(); 30: cb.addPacketListener(new PacketDebug()); 31: SyncPacketListener sync = new SyncPacketListener(cb); 32: Packet p = null; 33: SHA1Helper sha = new SHA1Helper(); 34: xsbuilder.setXMLNS(“jabber:component:accept”); 35: xsbuilder.setToAddress(new JID(server_host)); 36: Packet xmlpacket = xsbuilder.build(); 37: synchronized (sync) { 38: cb.send(xmlpacket); 39: p = sync.waitForType(xmlpacket, 5000); 40: } 41: if (p == null) 42: throw new RuntimeException(“unable to contact jabber server”); 43: String SessionID = cb.getSessionID(); 44: Handshake handshake = new Handshake(sha.digest(SessionID, secret));
159
160
Extending the Jabber Server
Listing 5.1 Continued 45: p = null; 46: sync.reset(); 47: synchronized (sync) { 48: cb.send(handshake); 49: p = sync.waitForType(handshake, 5000); 50: } 51: if (p == null) 52: throw new RuntimeException(“unable to handshake with server”); 53: cb.addPacketListener(this); 54: } 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76:
// PacketListener interface. Called when any packet is received. // Delegates IQ Browse and Search requests. public void receivedPacket(PacketEvent packetEvent) { Packet packet = packetEvent.getPacket(); if (packet instanceof InfoQuery) { InfoQuery iq = (InfoQuery) packet; try { JID loc = iq.getToAddress(); Enumeration enum = iq.Extensions(); while (enum.hasMoreElements()) { Extension ex = (Extension) enum.nextElement(); if (ex instanceof IQBrowse) handleBrowse(iq, loc); else if (ex instanceof IQSearchRequest) handleSearch(iq, (IQSearchRequest) ex, loc); continue; } } catch (Exception ex) { ex.printStackTrace(); } } }
77: 78: 79: 80:
// PacketListener interface. Called when any packet is sent. public void sentPacket(PacketEvent packetEvent) {} // PacketListener interface. Called when a packet send fails. public void sendFailed(PacketEvent packetEvent) {}
81: // Dispatch IQ browse requests based on the browse position 82: // (databases, tables, or fields) 83: private void handleBrowse(InfoQuery iq, JID to) 84: throws SQLException, InstantiationException { 85: String table = to.getResource(); 86: String database = to.getUsername();
A Database Service
Listing 5.1 Continued 87: if (database == null) { 88: listDatabases(iq, to); 89: } else if (table == null) { 90: listTables(database, iq, to); 91: } else { 92: listFields(database, table, iq, to); 93: } 94: } 95: // Browse into the list of databases served by this service. 96: private void listDatabases(InfoQuery iq, JID to) 97: throws SQLException, InstantiationException { 98: Connection conn = 99: DriverManager.getConnection(“jdbc:mysql://localhost/mysql”); 100: DatabaseMetaData dmd = conn.getMetaData(); 101: ResultSet rs = dmd.getCatalogs(); 102: IQBrowseBuilder bb = new IQBrowseBuilder(); 103: while (rs.next()) { 104: String dbname = rs.getString(1); 105: BrowseItemBuilder bib = new BrowseItemBuilder(); 106: bib.setName(dbname); 107: bib.setCategory(“service”); 108: bib.setJID(new JID(dbname, to.getServer(), null)); 109: bb.addChild(new BrowseItem(bib)); 110: } 111: bb.addNameSpace(“jabber:iq:browse”); 112: bb.setIQ(true); 113: bb.setCategory(“service”); 114: bb.setType(“db”); 115: bb.setName(“Database Catalog”); 116: InfoQueryBuilder iqb = new InfoQueryBuilder(); 117: iqb.addExtension(bb.build()); 118: iqb.setFromAddress(iq.getToAddress()); 119: iqb.setToAddress(iq.getFromAddress()); 120: iqb.setType(“result”); 121: iqb.setIdentifier(iq.getIdentifier()); 122: cb.send(iqb.build()); 123: conn.close(); 124: } 125: // Browse into the list of tables in a given database. 126: private void listTables(String database, InfoQuery iq, JID to) 127: throws SQLException, InstantiationException { 128: Connection conn = 129: DriverManager.getConnection(“jdbc:mysql://localhost/” + database);
161
162
Extending the Jabber Server
Listing 5.1 Continued 130: DatabaseMetaData dmd = conn.getMetaData(); 131: IQBrowseBuilder bb = new IQBrowseBuilder(); 132: ResultSet rs = dmd.getTables(null, null, “%”, null); 133: while (rs.next()) { 134: String tablename = rs.getString(“TABLE_NAME”); 135: String tabletype = rs.getString(“TABLE_TYPE”); 136: BrowseItemBuilder bib = new BrowseItemBuilder(); 137: bib.setName(tablename); 138: bib.setType(tabletype); 139: bib.setCategory(“service”); 140: bib.setIQ(true); 141: bib.setJID(new JID(database, to.getServer(), tablename)); 142: bb.addChild(new BrowseItem(bib)); 143: } 144: bb.setIQ(true); 145: bb.setCategory(“service”); 146: bb.addNameSpace(“jabber:iq:browse”); 147: bb.setType(“db”); 148: bb.setName(“Database “ + database); 149: InfoQueryBuilder iqb = new InfoQueryBuilder(); 150: iqb.addExtension(bb.build()); 151: iqb.setFromAddress(iq.getToAddress()); 152: iqb.setToAddress(iq.getFromAddress()); 153: iqb.setType(“result”); 154: iqb.setIdentifier(iq.getIdentifier()); 155: cb.send(iqb.build()); 156: conn.close(); 157: } 158: // Browse into the list of fields for a given table 159: private void listFields( 160: String database, 161: String table, 162: InfoQuery iq, 163: JID to) 164: throws SQLException, InstantiationException { 165: Connection conn = 166: DriverManager.getConnection(“jdbc:mysql://localhost/” + database); 167: DatabaseMetaData dmd = conn.getMetaData(); 168: IQBrowseBuilder bb = new IQBrowseBuilder(); 169: ResultSet rs = dmd.getColumns(null, null, table, null); 170: while (rs.next()) { 171: String columnname = rs.getString(“COLUMN_NAME”); 172: String columntype = rs.getString(“TYPE_NAME”); 173: BrowseItemBuilder bib = new BrowseItemBuilder();
A Database Service
Listing 5.1 Continued 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206:
bib.setName(columnname); bib.setType(columntype); bib.setCategory(“item”); bib.setJID(new JID(database, to.getServer(), table)); bb.addChild(new BrowseItem(bib)); } bb.setIQ(true); bb.addNameSpace(“jabber:iq:search”); bb.setCategory(“service”); bb.setType(“db”); bb.setName(“Database “ + database + “/” + table); InfoQueryBuilder iqb = new InfoQueryBuilder(); iqb.addExtension(bb.build()); iqb.setFromAddress(iq.getToAddress()); iqb.setToAddress(iq.getFromAddress()); iqb.setType(“result”); iqb.setIdentifier(iq.getIdentifier()); cb.send(iqb.build()); conn.close(); } // Dispatch IQ search requests. “get” to getFields(), “set” to search(). private void handleSearch(InfoQuery iq, IQSearchRequest ex, JID to) throws SQLException, InstantiationException { String table = to.getResource(); String database = to.getUsername(); if ((database == null) || (table == null)) { sendError(iq); } else if (iq.getType().equals(“get”)) { getSearchInstructions(iq, ex, database, table); } else if (iq.getType().equals(“set”)) { search(iq, ex, database, table); } }
207: // Error reply to a malformed search request 208: private void sendError(InfoQuery iq) throws InstantiationException { 209: InfoQueryBuilder iqb = new InfoQueryBuilder(); 210: iqb.setToAddress(iq.getFromAddress()); 211: iqb.setFromAddress(iq.getToAddress()); 212: iqb.setType(“error”); 213: iqb.setErrorCode(“400”); 214: iqb.setErrorText(“Bad Request”); 215: cb.send(iqb.build()); 216: }
163
164
Extending the Jabber Server
Listing 5.1 Continued 217: // Generate instructions for searching a table. 218: private void getSearchInstructions( 219: InfoQuery iq, 220: IQSearchRequest ex, 221: String database, 222: String table) 223: throws SQLException, InstantiationException { 224: Connection conn = 225: DriverManager.getConnection(“jdbc:mysql://localhost/” + database); 226: DatabaseMetaData dmd = conn.getMetaData(); 227: IQSearchRequestBuilder iqsrb = new IQSearchRequestBuilder(); 228: ResultSet rs = dmd.getColumns(null, null, table, null); 229: while (rs.next()) { 230: String columnname = rs.getString(“COLUMN_NAME”); 231: iqsrb.set(columnname, “”); 232: } 233: iqsrb.set( 234: “instructions”, 235: “Fill in search values for the table’s columns”); 236: InfoQueryBuilder iqb = new InfoQueryBuilder(); 237: iqb.addExtension(iqsrb.build()); 238: iqb.setFromAddress(iq.getToAddress()); 239: iqb.setToAddress(iq.getFromAddress()); 240: iqb.setType(“result”); 241: iqb.setIdentifier(iq.getIdentifier()); 242: cb.send(iqb.build()); 243: conn.close(); 244: } 245: // Search a table based on the parameters of the search request. 246: private void search( 247: InfoQuery iq, 248: IQSearchRequest ex, 249: String database, 250: String table) 251: throws SQLException, InstantiationException { 252: Connection conn = 253: DriverManager.getConnection(“jdbc:mysql://localhost/” + database); 254: Statement stmt = conn.createStatement(); 255: StringBuffer query = new StringBuffer(1024); 256: query.append(“select * from “); 257: query.append(table); 258: // Get the query params 259: Enumeration columns = ex.getNames(); 260: boolean addedWhere = false;
A Database Service
Listing 5.1 Continued 261: while (columns.hasMoreElements()) { 262: String column = (String) columns.nextElement(); 263: String value = ex.getValue(column); 264: if ((value == null) || value.equals(“”)) 265: continue; // field was left blank 266: if (!addedWhere) { 267: addedWhere = true; 268: query.append(“ where “ + column + “ like \’” + value + “\’”); 269: } else { 270: query.append(“ and “ + column + “ like \’” + value + “\’”); 271: } 272: } 273: ResultSet rs = stmt.executeQuery(query.toString()); 274: ResultSetMetaData rsmd = rs.getMetaData(); 275: int columnCount = rsmd.getColumnCount(); 276: IQSearchResultBuilder iqsrb = new IQSearchResultBuilder(); 277: while (rs.next()) { 278: SearchResultBuilder srb = new SearchResultBuilder(); 279: for (int col = 1; col —> ./libs/jsm.dll
This will cause the server to reject all registration requests.
Registration Data Okay, what if I disable arbitrary registration requests—how can I create new users? One way would be to temporarily enable registration and use a program, such as the one you’ve already seen, to create a bunch of new users. However, because the Jabber server stores its registration data as simple XML documents, it’s possible to generate new users offline. Here’s the XML document that was created in the server’s ./spool directory in a file called user1.xml after the client registered the user1 account: Registered password user1 password registered
As you can see, it’s mostly just the registration data that we provided and some timestamp information. Copy this file to user2.xml, edit it to change the to user2, and restart the server. Presto! New user. So it would be easy to write a script that went through a database or an employee list and created one of these files for each user.
Client Authentication
Client Authentication Before the Jabber server will provide services to any connection, the connection must be authenticated as a valid system user. Again, a “user” could represent a person or a system—they look the same to the server.The Jabber server has three built-in methods of authenticating users: plain authentication, digest authentication, and zero-knowledge authentication.These three methods differ in the data that the client and the server pass back and forth during the authentication interaction. In this section, we look at each one and also write one of our own. Client authentication is handled by the JSM service and the three authentication methods are configured in the jabber.xml file in the JSM section shown here.The three authentication modules are highlighted in bold: ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll ./libs/jsm.dll
You can comment out any of the authentication methods that you don’t want to support. CAUTION In the 1.4.2 version of jabberd, if you remove the mod_auth_plain JSM module, registration of new users doesn’t work.
233
234
Jabber Security
In all cases, the authentication begins with the client sending an IQ get request using the namespace jabber:iq:auth and its username.The document for user1 looks like this: user1
This tells the server that user1 would like to log in and requests the list of supported authentication methods. If the server is configured with all three forms of authentication, it responds with a packet like this: user1 494 3DE39085
The list of supported authentication methods is provided in the elements within the query element.The presence of certain elements in this packet tells the client that the server supports authentication types plain (), digest (), and zero-knowledge ( and ), and also tells the client that it should supply a resource to log in ().The client can choose any of the three authentication methods. Before looking in detail at the three authentication methods, Listing 6.2 shows a small abstract Java class that demonstrates how to authenticate to the Jabber server.The abstract method authenticate() is implemented three different ways in Listings 6.3 through 6.5. jabber:iq:auth
Client Authentication
Listing 6.2 Authentication Base Class 1:package org.jabber; 2: 3:import org.jabber.jabberbeans.*; 4:import org.jabber.jabberbeans.Extension.*; 5:import java.net.*; 6:import java.util.*; 7: 8:public abstract class AuthBase implements PacketListener { 9: ConnectionBean cb; 10: String server_host; 11: String username; 12: String password; 13: String resource; 14: 15: public AuthBase(String[] args) { 16: server_host = args[0]; 17: username = args[1]; 18: password = args[2]; 19: resource = args[3]; 20: cb = new ConnectionBean(); 21: cb.addPacketListener(this); 22: try { 23: cb.connect(InetAddress.getByName(server_host)); 24: InfoQueryBuilder iqb = new InfoQueryBuilder(); 25: IQAuthBuilder iqa = new IQAuthBuilder(); 26: iqa.setUsername(username); 27: iqb.setType(“get”); 28: iqb.addExtension(iqa.build()); 29: Packet iq = iqb.build(); 30: cb.send(iq); 31: } catch (Exception ex) { 32: ex.printStackTrace(); 34: } 35: 36: String authMsgId = null; 37: public void receivedPacket(PacketEvent pkt) { 38: if (pkt.getPacket() instanceof InfoQuery) { 39: InfoQuery iq = (InfoQuery) pkt.getPacket(); 40: if (iq.getIdentifier().equals(authMsgId)) { 41: if (iq.getErrorCode() == null) 42: System.out.println(“Successfully authenticated”); 43: else 44: System.out.println( 45: “Authentication failed: “ + iq.getErrorText()); 46: cb.disconnect();
235
236
Jabber Security
Listing 6.2 Continued 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64:}
System.exit(0); } Enumeration enum = iq.Extensions(); while (enum.hasMoreElements()) { Extension ext = (Extension) enum.nextElement(); if (ext instanceof IQAuth) { authMsgId = authenticate((IQAuth) ext); } } } } protected abstract String authenticate(IQAuth ext); /* These methods are required by the PacketListener interface */ public void sendFailed(PacketEvent arg0) {} public void sentPacket(PacketEvent arg0) {}
The constructor (starting at line 15) parses the command line arguments into the server host, username, password, and resource to use for authentication. Lines 20–23 create a connection to the server and set up this object as a packet listener on the connection. Lines 24–30 create and send an IQ get packet, as in the example XML that preceeds Listing 6.2. When a response is received, the receivedPacket() method is called. Lines 40–48 check to see whether the packet is the jabber:iq:auth set response and, if so, print whether the authentication was successful or not and exit.The set IQ request for which it’s looking is sent by the abstract method authenticate() at line 53. It’s up to the three inheriting classes to implement authenticate() and demonstrate the three authentication methods.
Plain Authentication Plain authentication is the simplest form of authentication and the easiest to understand and implement. In plain authentication, the client simply passes its username and password in plaintext (unencrypted) in a packet like this: user1 password
Client Authentication
Work
Here the user authenticating is user1 and its password is simply password.The server replies with a result packet like this if the authentication is successful:
If authentication fails for some reason, the server replies with an error IQ packet that looks like this: user1 bad-password Work Unauthorized
Here the error code 401 means that the authentication failed for some reason. Note that the Jabber server returns the same error code for a wrong password as it does for a non-existent user.This makes it harder for potential hackers to guess valid usernames than if it returned a packet that said I know this user, but the password is wrong. The example in Listing 6.3 extends the base class shown in Listing 6.2 above to demonstrate plain authentication. Listing 6.3 Plain Authentication 1:package org.jabber; 2: 3:import org.jabber.jabberbeans.*; 4:import org.jabber.jabberbeans.Extension.*; 5: 6:public class AuthPlain extends AuthBase { 7: 8: public AuthPlain(String[] args) {
237
238
Jabber Security
Listing 6.3 Continued 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:}
super(args); } public static void main(String[] args) { new AuthPlain(args); } protected String authenticate(IQAuth ext) { try { InfoQueryBuilder iqb = new InfoQueryBuilder(); IQAuthBuilder iab = new IQAuthBuilder(); iqb.setType(“set”); iab.setUsername(username); iab.setPassword(password); iab.setResource(resource); iqb.addExtension(iab.build()); InfoQuery myiq = (InfoQuery) iqb.build(); String authMsgId = myiq.getIdentifier(); cb.send(myiq); return authMsgId; } catch (InstantiationException ex) { ex.printStackTrace(); } return null; }
The authenticate() method (starting at line 14) implements the plain authentication. It simply creates an IQ set packet with a jabber:iq:auth extension (line 17) and fills in the username, password, and resource tags. Pretty simple. Although plain authentication is simple to implement, it has some serious shortcomings. Most obviously, the password is transmitted in the clear (unencrypted) from the client to the server. Anyone with access to the network between the two computers can easily learn the password. Less obvious is the risk posed by the implementation of the mod_auth_plain component, which stores the unencrypted password in the XDB database.There it (and every other user’s password) is vulnerable to anyone who can get access to the XDB database. It’s unfortunate that mod_auth_plain doesn’t store a cryptographic hash of the password rather than the password itself.We’ll see more about cryptographic hashes in the next sections.
Digest Authentication A digest is another name for a cryptographic hash. A digest algorithm has the following properties: The same input data always produces the same output data.The output is usually a fixed size and smaller than the expected input data (hence a “digest”). n
Client Authentication
n
n
n
A small change in the input data has a large and unpredictable effect on the output data. It is unlikely that two inputs will produce the same output. The input data cannot be deduced from the output data. Sometimes these algorithms are called “one-way” algorithms, because it’s easy to compute the output given the input, but hard to compute the input given the output.
There are several common hash algorithms and Jabber uses one called SHA-1, which is widely available and considered quite strong.These algorithms are very useful for handling passwords because you can make a hash of a password and then give the hash to someone else who can verify whether you actually know the password without you actually telling them the password. Take Figure 6.1 as an example. Suppose Alice and Bob share a password, and Bob needs to verify whether Alice knows the correct password.
Figure 6.1 Password exchange via digest.
If Alice just tells Bob the password, then he can tell whether she knows it or not, but if Bob didn’t really know the password—oops, he does now. Using digests avoids this problem; Alice and Bob independently hash their passwords and exchange the hashes.This way neither actually has to give the password to the other.
239
240
Jabber Security
There needs to be a little more to this, however. After Alice gives Bob the hash of the password, that’s really all he needs to exchange passwords with anyone else.They need to add some more data to the password before they hash it, so they can be sure that the other party actually carried out the hash and didn’t just learn the hash result somewhere else. Figure 6.2 shows how this works.
%
!
!
#
"
$ $
Figure 6.2 Better password exchange via digest.
Here Alice and Bob agree on some random data to append to the password before hashing it.This ensures that the hash output is different every time. The Jabber server uses this method to do digest authentication. Under digest authentication, the client appends the password to the session ID (from the stream header) and creates a hash of this string to use for authentication.The session ID is serving as the random addition to the hash input function as “1234” did in Figure 6.2. As a reminder, the stream header from the server looks something like this (the stream ID is highlighted):
Client Authentication
If is present in the reply to the jabber:iq:auth get query, the client knows that the server supports digest authentication, appends the password to the stream ID, hashes the result (3DE3AC8Fpassword, in this case), and sends a packet like this, which includes a element—shown in bold—rather than a element used in plain authentication: user1 30118fe0f80503fe4beb8277f797b7318942a07d Work
The server does the same, and if its hash matches the client’s hash, then the client is successfully authenticated. Listing 6.4 shows the Java code to implement digest authentication with the lines that differ from plain authentication highlighted in bold. Listing 6.4 Digest Authentication 1:package org.jabber; 2: 3:import org.jabber.jabberbeans.*; 4:import org.jabber.jabberbeans.Extension.*; 5:import org.jabber.jabberbeans.util.*; 6: 7:public class AuthDigest extends AuthBase { 8: public AuthDigest(String[] args) { 9: super(args); 10: } 11: public static void main(String[] args) { 12: new AuthDigest(args); 13: } 14: 15: protected String authenticate(IQAuth ext) { 16: try { 17: SHA1Helper sha1helper = new SHA1Helper(); 18: String sessionID = cb.getSessionID(); 19: String digest = sha1helper.digest(sessionID, password); 20: IQAuth iqa = (IQAuth) ext;
241
242
Jabber Security
Listing 6.4 Continued 21: InfoQueryBuilder iqb = new InfoQueryBuilder(); 22: IQAuthBuilder iab = new IQAuthBuilder(); 23: iqb.setType(“set”); 24: iab.setUsername(username); 25: iab.setSHA1Digest(digest); 26: iab.setResource(resource); 27: iqb.addExtension(iab.build()); 28: InfoQuery myiq = (InfoQuery) iqb.build(); 29: String authMsgId = myiq.getIdentifier(); 30: cb.send(myiq); 31: return authMsgId; 32: } catch (InstantiationException ex) { 33: ex.printStackTrace(); 34: } 35: return null; 36: } 37:}
The authenticate() method in this class (lines 15–36), uses a helper class to calculate the SHA-1 hash.The SHA1Helper class is part of the JabberBeans library and an instance of this class is created at line 17. At line 18, we get the stream ID from the ConnectionBean, which stored it when it parsed the stream header. Line 19 calculates the hash (digest) of the stream ID and password; then it’s simply a matter of assembling an IQ packet with a jabber:iq:auth extension, adding the tag (line 25), and sending it. If the password is correct, we should get the no-error response from the server. Digest authentication is an improvement over plain authorization, because now someone listening to the interchange over the network can’t learn the password. He could see the digest value, but it’s valid for only this session, so it’s not too useful. However, this method still has the problem that the password must be stored on the server, and it is stored unencrypted in the XDB database.
Zero-Knowledge Authentication Zero-knowledge authentication attempts to improve on the other types of authentication by not requiring the server to store the clients’ passwords. When a client registers with the server, the mod_auth_0k module in the JSM takes the password and a random string and hashes them together just as the digest authentication does when it’s authenticating a client. It goes further, though, and takes that result and hashes it again, takes that result and hashes it again, and so on, some number of times and stores the result of the iterative hashing, along with the number of times it hashed and the random string. It doesn’t need to store the password itself—only the client needs to know the password.
Client Authentication
Then, when a client wants to authenticate, it asks the server for the random string and the number of times it hashed, combines the string and the password, and performs the same iterative hashing operation, but stops one iteration short. So, if the server’s iteration count is 400, the client performs 399 hashes and sends the result to the server. The server can then perform one more hash on the data and see whether it matches what it has stored. If it does, then authentication was successful and the server can remember the client’s hash value and decrement the iteration counter for the next time. The next time this client authenticates, it will do the iterative hash 398 times and the server will do the 399th. These data elements are encoded in the XML exchanged between the client and the server. As you recall, here’s an example of the XML packet that the server sends the client in response to a jabber:iq:auth get request with the zero-knowledge data highlighted: user1 494 3DE39085
The parts of interest for zero-knowledge authentication are highlighted in bold.The element (494 here) is the count of the number of times the server has hashed, and (3DE39085 here) is the random string that the server added to the password before hashing it when the password was registered. The client takes these two values and the password, hashes 493 times, and comes up with an authentication packet, like this:
user1 9a9bf2b4f453c52aa584874c17cdc83a2d3f1652
243
244
Jabber Security
Work
The result of the iterative hashing is in the highlighted tag.The server takes this value, hashes it one more time, and if it matches its stored value, authentication is successful; the server stores the hash value provided by the client, decrements the sequence counter, and responds with a no-error result packet. Listing 6.5 shows the authenticate() method that performs zero-knowledge authentication. Listing 6.5 Zero-Knowledge Authentication 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
protected String authenticate(IQAuth ext) { try { SHA1Helper sha1helper = new SHA1Helper(); String token = ext.getZeroKToken(); int sequence = ext.getZeroKSequence(); String hashedPassword = sha1helper.digest(password, null); String hash = sha1helper.digest(hashedPassword, token); while (sequence— > 0) { hash = sha1helper.digest(hash,null); } IQAuth iqa = (IQAuth) ext; InfoQueryBuilder iqb = new InfoQueryBuilder(); IQAuthBuilder iab = new IQAuthBuilder(); iqb.setType(“set”); iab.setUsername(username); iab.setZeroKHash(hash); iab.setResource(resource); iqb.addExtension(iab.build()); InfoQuery myiq = (InfoQuery) iqb.build(); String authMsgId = myiq.getIdentifier(); cb.send(myiq); return authMsgId; } catch (InstantiationException ex) { ex.printStackTrace(); } return null; }
The zero-knowledge data from the server is processed at lines 4 and 5, and the zeroknowledge hash is added to the response packet at line 16. Although zero-knowledge has the advantage of not requiring the transmission or storage of the password, its disadvantage is that you get only a limited number of logins
A Custom Authentication Component
(until the sequence counter reaches 1) before the password must be reset.This is a good example of trading some convenience for security. CAUTION Even if you disable mod_auth_plain and mod_auth_digest, the 1.4.2 server will still store the password in the XDB database. Other parts of the JSM depend on a entry to determine whether a username is valid. If you’re really concerned about storing passwords on the server, you can stop the server, edit the user XML files, and replace the password text with some bogus text like “XXX.” Zeroknowledge authentication will still work.
A Custom Authentication Component In this section, we implement our own server component to do customized authentication. Let’s suppose that we want to manage our clients’ passwords outside the XDB database.We want to have a list of passwords for each client, and when the client logs in using one password, the next time we require the next one in the list. So if the client’s password list looks like this: bluebird helloworld artichoke
The first login will use bluebird as the password, the second will use helloworld, and the third will use artichoke. After that, we’ll have to distribute a new set of passwords before the client can successfully authenticate again.This is the similar to the zeroknowledge authentication method described earlier, except we’re choosing the passwords rather than generating them with SHA-1.You’ll see later in this section that this technique could be adapted to use other password sources, such as a corporate directory service, for example. We configure the Jabber server to use our component for authentication by editing the jabber.xml file. First, we add our new authenticator as a service that will connect to the server on a network port by adding this section to the main section of jabber.xml (that is, not inside the JSM service tag): 9002/port> secret
This tells the Jabber server that a service called myauth.my-jabber will connect on port 9002 and use the password secret to authenticate. Services use a modified form of digest authentication when they connect to the service, which you’ll see in the following section.
245
246
Jabber Security
Now we need to tell the JSM to route authentication requests to the new service.To do this, we add a new tag in the JSM configuration section of jabber.xml, inside the tag, that looks like this: myauth.my-jabber
The contents of this element should match the ID of the service defined in the configuration element.This will cause all registration and authentication requests (IQ packets bearing the jabber:iq:register and jabber:iq:auth namespaces) to be routed to the myauth.my-jabber component.
Connecting the Authentication Service Before the authentication service can authenticate clients, it must authenticate itself to the Jabber server.There is a special protocol for authenticating services that is similar to the digest client authentication. It starts with the connecting service sending a stream header packet to the server. Here’s an example:
Notice that the XML namespace is jabber:component:accept, rather than as it is for client connections. Also, unlike the client connection, the connecting service sends the server a session ID. The server responds with a matching stream header of its own that looks like this: jabber:client
The server uses the same XML namespace and adds its own session ID. The next thing that happens is the service authentication handshake.The handshake value is the SHA-1 digest of the server’s session ID with the shared secret appended to it. So if, as in this XML stream example, the server’s session ID is 3DE3D81A and the shared secret is secret, the handshake value is the SHA-1 hash of 3DE3D81Asecret.To initiate the handshake, the connecting service sends a special handshake packet containing the digest like this: 965ebc5956daee3da839c1f2a19d64d5087d3689
The server computes the same handshake value and compares it to the one in the handshake packet. If they match, the service is authenticated and the server responds with a packet like this:
If they don’t match, the server responds with an error packet like this: Invalid handshake
and closes the connection.
A Custom Authentication Component
Handling Authorization Packets So what do these authorization packets look like? The IQ packets that are sent from the client are wrapped in a packet before being delivered to the component. As you saw in Chapter 4, “Jabber Server Architecture,” the server uses route packets to route messages internally between its components. By the time it gets to our authentication component, an IQ get request looks like this: user1
The contents of the route packet tell us that this was originally sent from the c2s (client-to-server) component. Inside the route wrapper is the IQ packet that the client originally sent.We need to reply via the c2s component with a wrapped IQ response packet, like this: user1
As you saw earlier, because this packet includes only the tag and not the tags, this tells the client that we support only plain (password-only) authentication.The client can then send the server an IQ set jabber:iq:auth request, which will be routed to the authentication service like this: , , or
user1 password
247
248
Jabber Security
Work
This is just the IQ set message as sent by the client wrapped in a route packet. At this point, the authentication service can decide whether to grant the client access to the server or not. If the client credentials match, we can reply with a packet like this: user1 password Work
This creates a session in the JSM for this user. If the credentials don’t match, we reply with an error packet like this (notice the highlighted error element): Unauthorized user1 password Work
A Custom Authentication Component
Either way, the wrapped IQ packet gets routed through the c2s component and back to the client. Here’s the Java code for the authentication component. It’s fairly long, so we’ll list the whole thing here and then go through it step by step. Listing 6.6 A Custom Authentication Service 1:package org.jabber; 2: 3:import java.io.*; 4:import java.net.*; 5:import java.util.*; 6:import org.jabber.jabberbeans.*; 7:import org.jabber.jabberbeans.Extension.*; 8:import org.jabber.jabberbeans.serverside.*; 9:import org.jabber.jabberbeans.util.*; 10: 11:public class AuthService implements PacketListener { 12: private static final String BASE_ACCEPT_XMLNS = ➥”jabber:component:accept”; 13: private static final String sessionID = “arbitrary”; 14: private static final String service_name = “myauth”; 15: 16: ConnectionBean cb; 17: String server_host; 18: int port; 19: String secret; 20: public AuthService(String[] args) { 21: server_host = args[0]; 22: port = Integer.parseInt(args[1]); 23: secret = args[2]; 24: cb = new ConnectionBean(); 25: try { 26: setup(); 27: } catch (Exception ex) { 28: ex.printStackTrace(); 29: } 30: } 31: 32: private void setup() 33: throws 34: IOException, 35: InstantiationException, 36: UnknownHostException, 37: InterruptedException { 38: cb.addPacketListener(new PacketDebug()); 39: SyncPacketListener sync = new SyncPacketListener(cb);
249
250
Jabber Security
Listing 6.6 Continued 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84:
Packet p; SHA1Helper sha = new SHA1Helper(); cb.disableStreamHeader(); cb.connect(InetAddress.getByName(server_host), port); XMLStreamHeaderBuilder xsbuilder = new XMLStreamHeaderBuilder(); xsbuilder.setXMLNS(BASE_ACCEPT_XMLNS); xsbuilder.setIdentifier(sessionID); xsbuilder.setFromAddress(new JID(service_name)); xsbuilder.setToAddress(new JID(server_host)); Packet xmlpacket = xsbuilder.build(); // Send the XML stream header synchronized (sync) { cb.send(xmlpacket); p = sync.waitForType(xmlpacket, 5000); } String sessionID = cb.getSessionID(); Handshake handshake = new Handshake(sha.digest(sessionID, secret)); // Send the handshake p = null; sync.reset(); synchronized (sync) { cb.send(handshake); p = sync.waitForType(handshake, 5000); } // Start servicing packets cb.addPacketListener(this); } /* * Called when any packet is received */ public void receivedPacket(PacketEvent packetEvent) { Packet packet = packetEvent.getPacket(); if (packet instanceof Route) { Route route = (Route) packet; Enumeration extensions = route.Extensions(); while (extensions.hasMoreElements()) { Extension ext = (Extension) extensions.nextElement(); if (ext instanceof InfoQuery) { InfoQuery iq = (InfoQuery) ext;
A Custom Authentication Component
Listing 6.6 Continued 85: Enumeration iqexts = iq.Extensions(); 86: while (iqexts.hasMoreElements()) { 87: Extension iqext = (Extension) ➥iqexts.nextElement(); 88: if (iqext instanceof IQAuth) { 89: IQAuth iqa = (IQAuth) iqext; 90: if (iq.getType().equals(“get”)) { 91: replyWithLogin(route, iq, iqa); 92: } else { 93: login(route, iq, iqa); 94: } 95: } else if (iqext instanceof IQRegister) { 96: IQRegister iqr = (IQRegister) iqext; 97: replyWithError(route, iq, iqr); 98: } 99: } 100: } 101: } 102: } 103: } 104: 105: /* 106: * Respond to “get” queries. We do only plain authentication 107: */ 108: private void replyWithLogin(Route route, InfoQuery iq, IQAuth iqa) { 109: try { 110: RouteBuilder rb = new RouteBuilder(); 111: InfoQueryBuilder iqb = new InfoQueryBuilder(); 112: IQAuthBuilder iab = new IQAuthBuilder(); 113: iab.setPassword(“”); 114: iab.setUsername(iqa.getUsername()); 115: iqb.addExtension(iab.build()); 116: rb.setFromAddress(route.getToAddress()); 117: rb.setToAddress(route.getFromAddress()); 118: rb.setType(“auth”); 119: iqb.setType(“result”); 120: iqb.setIdentifier(iq.getIdentifier()); 121: rb.addPacket(iqb.build()); 122: cb.send(rb.build()); 123: } catch (InstantiationException ex) { 124: ex.printStackTrace(); 125: } 126: } 127: 128: /*
251
252
Jabber Security
Listing 6.6 Continued 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173:
* Respond to “set” queries. Do plain authentication */ private void login(Route route, InfoQuery iq, IQAuth iqa) { try { RouteBuilder rb = new RouteBuilder(); InfoQueryBuilder iqb = new InfoQueryBuilder(); rb.setFromAddress(route.getToAddress()); rb.setToAddress(route.getFromAddress()); rb.setType(“auth”); iqb.setIdentifier(iq.getIdentifier()); if (isValid(iqa.getUsername(), iqa.getPassword())) { iqb.setType(“result”); // OK } else { iqb.setType(“error”); // not OK iqb.setErrorCode(“401”); iqb.setErrorText(“Unauthorized”); } iqb.addExtension(iqa); rb.addPacket(iqb.build()); cb.send(rb.build()); } catch (InstantiationException ex) { ex.printStackTrace(); } } /* * Actually read the password file and compare it to the one given. * Passwords are in .pad * Current password counter is in .cnt */ private boolean isValid(String username, String password) { boolean ret = false; String realPassword = null; RandomAccessFile padFile = null; int passwordCount = 1; try { padFile = new RandomAccessFile(username + “.pad”, “r”); } catch (FileNotFoundException fnfex) { return false; // no password file for this user } RandomAccessFile counterFile = null; try { File counter = new File(username + “.cnt”); boolean cntExists = counter.exists();
A Custom Authentication Component
Listing 6.6 Continued 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218:
counterFile = new RandomAccessFile(counter, “rw”); if (cntExists) { String cntString = counterFile.readLine(); cntString = cntString.trim(); passwordCount = Integer.parseInt(cntString); counterFile.seek(0); } for (int i = 0; i < passwordCount; i++) { realPassword = padFile.readLine(); } if (realPassword == null) { // end-of-file log(username + “ is out of passwords”); } else if (realPassword.equals(password)) { // OK! counterFile.writeChars(Integer.toString(passwordCount + 1)); ret = true; } } catch (Exception ex) { ex.printStackTrace(); } finally { if (counterFile != null) try { counterFile.close(); } catch (IOException oh_well) { } } return ret; } /* * We don’t handle registration requests. Report the error. */ private void replyWithError(Route route, InfoQuery iq, IQRegister iqr) { try { RouteBuilder rb = new RouteBuilder(); InfoQueryBuilder iqb = new InfoQueryBuilder(); rb.setFromAddress(route.getToAddress()); rb.setToAddress(route.getFromAddress()); rb.setType(“auth”); iqb.setIdentifier(iq.getIdentifier()); iqb.setType(“error”); // not ok iqb.setErrorCode(“403”); iqb.setErrorText(“Forbidden”); iqb.addExtension(iqr); rb.addPacket(iqb.build()); cb.send(rb.build());
253
254
Jabber Security
Listing 6.6 Continued 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242:}
} catch (InstantiationException ex) { ex.printStackTrace(); } } /* * Write a warning message to the log service. */ private void log(String msg) { LogBuilder lb = new LogBuilder(); lb.setType(“warn”); lb.setFromAddress(new JID(service_name)); lb.setContent(msg); cb.send(lb.build()); } // Extra methods required by PacketListner interface public void sendFailed(PacketEvent packetEvent) {} public void sentPacket(PacketEvent packetEvent) {} public static void main(String[] args) { new AuthService(args); }
Let’s take this a piece at a time. After the imports and other Java housekeeping, the real action starts in the setup() method at line 32. As you saw before, at line 38 we create a PacketDebug and add it as a listener to the ConnectionBean so we can see the packets as they come in and out.We also create a SyncPacketListener, which lets us synchronously wait for particular types of packets to arrive. Because this is a service connection and not a client connection, we need to disable the automatic generation of the client stream header in the ConnectionBean with the call to cb.disableStreamHeader() at line 43. Lines 46–51 set up the stream header as shown in the XML stream header example shown earlier in this section.Then we send the packet and wait up to five seconds (5000 milliseconds) for the response at lines 54–57. Next, (lines 59 and 60) calculate the handshake by getting the session ID from the connection bean and hashing it with the secret using the SHA1Helper.Then, lines 63–68 send the handshake and wait for the response. Line 70 adds this object as a packet listener to the connection bean so that we start receiving packets at the receivedPacket() method.
A Custom Authentication Component
The receivedPacket() method (lines 76–103) follows setup() and is not nearly as complicated as it looks. It simply takes each route packet, looks inside it, and dispatches it in one of these four ways: Authorization get requests go to replyWithLogin(). Authorization set requests go to login(). Registration requests go to replyWithError(). Everything else is ignored. n
n
n
n
Now for the important parts.We need to reply appropriately to jabber:iq:auth get queries so clients know that they should just send us passwords and not hash them somehow.The replyWithLogin() method (repeated here so you don’t have to keep flipping back) builds up a route packet (line 110), which contains an IQ packet (line 111), which contains an auth extension (line 112). 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126:
private void replyWithLogin(Route route, InfoQuery iq, IQAuth iqa) { try { RouteBuilder rb = new RouteBuilder(); InfoQueryBuilder iqb = new InfoQueryBuilder(); IQAuthBuilder iab = new IQAuthBuilder(); iab.setPassword(“”); iab.setUsername(iqa.getUsername()); iqb.addExtension(iab.build()); rb.setFromAddress(route.getToAddress()); rb.setToAddress(route.getFromAddress()); rb.setType(“auth”); iqb.setType(“result”); iqb.setIdentifier(iq.getIdentifier()); rb.addPacket(iqb.build()); cb.send(rb.build()); } catch (InstantiationException ex) { ex.printStackTrace(); } }
Lines 113–121 assemble the various parts of the reply into a packet that looks like this: user1
255
256
Jabber Security
The tells clients that they should authenticate with plain authentication. The login() method is called by receivedPacket() when a client is attempting to authenticate. It is repeated for convenience here: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153:
private void login(Route route, InfoQuery iq, IQAuth iqa) { try { RouteBuilder rb = new RouteBuilder(); InfoQueryBuilder iqb = new InfoQueryBuilder(); rb.setFromAddress(route.getToAddress()); rb.setToAddress(route.getFromAddress()); rb.setType(“auth”); iqb.setIdentifier(iq.getIdentifier()); if (isValid(iqa.getUsername(), iqa.getPassword())) { iqb.setType(“result”); // OK } else { iqb.setType(“error”); // not OK iqb.setErrorCode(“401”); iqb.setErrorText(“Unauthorized”); } iqb.addExtension(iqa); rb.addPacket(iqb.build()); cb.send(rb.build()); } catch (InstantiationException ex) { ex.printStackTrace(); } }
As in the previous method, we need to build a route packet, which contains an IQ packet, which contains an auth extension.We create the route packet and initialize it with the addresses and type, and set the identifier on the IQ packet to match the IQ we were given (lines 135–138).Then we call isValid() to check the password, and if it is correct, we set the IQ packet’s type to result (that is, no error). If it is not, we set its type to error and fill in the error code and text. Our task is made a little easier because we just parrot back the jabber:iq:auth extension that the client gave us (line 147).Then we assemble the packet and send it on its way (line 149). The next method in Listing 6.6 is the isValid() method, which returns true if the username/password match our records and false if they don’t. It’s not worth going through the ugliness of Java file I/O and text processing; suffice it to say that this method looks through the password file for this username, finds the next unused one, and compares it to the one given. One interesting thing to note, though, is that if the user runs out of passwords, the service doesn’t tell the user (that’s too much information for a potential attacker), but it does notify the system administrator by creating a log message. It calls the log() method, which is repeated here:
A Custom Authentication Component
227: 228: 229: 230: 231: 232: 233:
private void log(String msg) { LogBuilder lb = new LogBuilder(); lb.setType(“warn”); lb.setFromAddress(new JID(service_name)); lb.setContent(msg); cb.send(lb.build()); }
Creating log messages is so easy that it makes sense to use them to help the system operators.When a user runs out of passwords, this message appears on the console and in the error.log file: 20021126T20:23:16: [warn] (myauth): user1 is out of passwords
The only part of this service that we haven’t talked about is the replyWithError() method. It is called if we receive a registration request. If new user registration is disabled (as we showed earlier in this chapter), then no registration packets will get to this service, but it’s good to be ready to respond to them anyway. If we get a registration request, the service responds with a route packet that looks like this: Forbidden bluebird Work user1
The highlighted element should tell the client that user registration is not allowed. There are lots of easy changes that could make this service more useful in certain situations. For example, Instead of reading passwords from a file, read them from the corporate directory service so users can use the same password for their Jabber accounts. Store the passwords on the server hashed so that if they are disclosed, they can’t be used to gain access. Handle digest authentication rather than plain authentication. Handle the registration of new users. Generate a new set of passwords when the old ones run out, encrypt them, and email them to the user. n
n
n
n
n
257
258
Jabber Security
Using SSL for Client Connections The previous sections showed how authentication is handled between the Jabber server and its components and clients. Authentication gives the server assurance that it’s talking to who it thinks it’s talking to, which is one important aspect of security, but not the only aspect. In this section, we turn to confidentiality, or ensuring that communications between parties is kept secret from other parties, and integrity, or ensuring that data transmitted between parties is not modified in transit. Fortunately, the Jabber server has built-in support for confidentiality and integrity between the server and its clients through its support for Secure Sockets Layer (SSL) encryption. Data sent over connections made with SSL are encrypted before being transmitted and decrypted by the recipient.This is the same technology that protects secure Web pages.
SSL Overview Although SSL can be used to provide authentication as well as confidentiality and integrity, the Jabber server uses it only for confidentiality and integrity and handles authentication as described earlier. SSL services use public key certificates to manage the exchange of information between the client and the server. A certificate contains information about the certified entity (the Jabber server, in our case) and its public cryptographic key.The certificate is cryptographically signed so that the recipient can verify that the key in the certificate really belongs to the certificate’s owner. NOTE If you want more details on certificates, SSL, and public key encryption, a great place to start is the book Applied Cryptography by Bruce Schneier.
The client initiates the connection, which proceeds as shown in Figure 6.3 and described in the following steps: NOTE Figure 6.3 doesn’t show all the possible SSL messages—only the ones used by the Jabber server.
1. ClientHello. The client sends the SSL version number it supports, a random number (used to create the session key), a session ID, and its set of cipher algorithms. 2. ServerHello. The server sends its version number, another random number, the session ID, and its choice of cipher algorithms from the set that the client supports. 3. Certificate. The server sends its public key certificate so the client can verify its identity. 4. ServerHelloDone. The server completes its first part of the handshake.This tells the client to not expect any other optional data.
Using SSL for Client Connections
Client
Server
ClientHello
ServerHello Certificate ServerHelloDone ClientKeyExchange ClientChangeCipherSpec ClientFinished ServerChangeCipherSpec
ServerFinished Jabber Messages
Figure 6.3 SSL handshake.
5. ClientKeyExchange. The client sends the server the key material necessary, which varies depending on the cipher algorithm chosen.The key material is encrypted with the server’s public key. 6. ClientChangeCipherSpec. The client notifies the server that it is changing to the specified cipher algorithm. 7. ClientFinished. The client completes this part of the handshake. 8. ServerChangeCipherSpec. The server notifies the client that it is changing to the specified cipher algorithm. 9. ServerFinished. The handshake is completed. 10. Jabber Messages. Jabber messages flow across the encrypted channel.
259
260
Jabber Security
Enabling SSL in the Jabber Server Before we can turn on SSL, we need to build jabberd with SSL enabled, and before we can build the server, we need to make sure that OpenSSL is installed. On a Linux machine, we can use the rpm command to see whether it’s installed: $ rpm -q -a|grep openssl openssl-devel-0.9.6b-29 openssl-perl-0.9.6b-29 openssl-0.9.6b-29
The openssl-perl package is not required, but the other two are needed to build jabberd with SSL. Of course, your version numbers may be different than these. If you don’t have these packages, you can get them from http://www.openssl.org. After OpenSSL is installed, you can configure jabberd to support SSL.You need to rerun the configure command in the Jabber installation directory. NOTE Chapter 2 contains step-by-step instructions for building jabberd. This section describes the changes to that build process needed to support SSL.
Run the like this:
configure
command with the argument
—enable-ssl
and it should look
$ ./configure —enable-ssl Running Jabber Configure ======================== Searching for SSL.. Getting pth settings...
Found.
A bunch more output… then Setting Build Parameters.. Done. Generating Settings Script.. Done. You may now type ‘make’ to build your new Jabber system.
If configure reports a Not Found error after it prints Searching for SSL..., you may not have OpenSSL installed correctly. After configure successfully completes, you can build jabberd by typing make, like this: $ make Making all in pthsock make[1]: Entering directory ‘/usr/local/jabber-1.4.2/pthsock’
Using SSL for Client Connections
gcc -g -Wall -I. -I. -I/usr/include/openssl -DHAVE_SSL ➥-I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 -fPIC -I../jabberd/ ➥-c -o client.o client.c gcc -g -Wall -I. -I. -I/usr/include/openssl -DHAVE_SSL ➥-I/usr/local/jabber-1.4.2/jabberd/pth-1.4.0 -fPIC -I../jabberd/ ➥-shared -o pthsock_client.so client.o -L/usr/lib -lssl -lcrypto -ldl –lresolv
Notice that the compile commands now contain the include path for the SSL headers and references to the SSL crypto libraries. The server needs a certificate to send to the clients, so we’ll use the OpenSSL tools to build one. Run the following command and follow the instructions to create the keys and certificate.You need to provide a passphrase (at least 4 characters) and the X500 Distinguished Name that you want to use. Distinguished Names are used to uniquely identify certificate holders and are usually based on characteristics such as location and affiliation. Its contents are not important to the operation of the Jabber server. If you don’t want to fill in any of the Distinguished Name fields, just enter a dot (.). $ openssl req -new -x509 -newkey rsa:1024 -days 365 ➥-keyout privkey.pem -out key.pem Using configuration from /usr/share/ssl/openssl.cnf Generating a 1024 bit RSA private key ........++++++ ...................................++++++ writing new private key to ‘privkey.pem’ Enter PEM pass phrase:**** Verifying password - Enter PEM pass phrase:**** ——You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter ‘.’, the field will be left blank. ——Country Name (2 letter code) [GB]:US State or Province Name (full name) [Berkshire]:Virginia Locality Name (eg, city) [Newbury]:Arlington Organization Name (eg, company) [My Company Ltd]:SAMS Organizational Unit Name (eg, section) []:. Common Name (eg, your name or your server’s hostname) []:my-jabber Email Address []:. $
The arguments to this command tell OpenSSL that we want to generate a new (X.509 (-x509) certificate, and that it should generate a new 1024-bit key for use with the RSA algorithm (-newkey –rsa:1024).The certificate should be valid for one
new)
261
262
Jabber Security
year (-days 365), put the private key in privkey.pem and the certificate in key.pem (-keyout privkey.pem -out key.pem).The next command strips out the passphrase so you don’t have to provide it to the server.You need to give it the passphrase you used in the previous step. $ openssl rsa -in privkey.pem -out privkey.pem read RSA key Enter PEM pass phrase:**** writing RSA key $
The Jabber server expects to find the private key appended to the certificate file, so we just concatenate the certificate and private key and remove the private key file: cat privkey.pem >> key.pem rm privkey.pem
Now the certificate and key are in the file key.pem. Naturally, the certificate file needs to be protected or someone could impersonate your server. It’s a good idea to make the file readable only by the user that you use to run jabberd. Next, you have to configure the server to open its client sockets for SSL connections. This requires editing the jabber.xml file. Find the section and add a section like this: /usr/local/jabber-1.4.2/key.pem
Substitute the IP address of your service for 192.168.1.1 and the actual path to your certificate file for /usr/local/jabber-1.4.2/key.pem.Then configure the c2s service to use SSL on one or more client connection ports. Find the section within the element and add a line like this: 192.168.1.1
The IP address should match the SSL IP address in the section.The port is arbitrary, but can’t be the same as any other service on the computer (including the non-SSL c2s ports). Port 5223 is common for Jabber SSL connections. It would be nice to be able to disable non-SSL connections, but that’s not possible with the 1.4.2 c2s component. If you delete the tags, c2s will open the default port (5222) for unencrypted communications.You can open the default port and listen for only local connections (that is, connections from clients on the same host as the server) by including this line in the c2s configuration: 127.0.0.1
This will prevent random people from all over the Internet from connecting to your server. Alternatively, the great thing about open source software such as jabberd is that
Using SSL for Client Connections
you can modify the c2s component itself to disable opening unsecured ports and then recompile jabberd. Save jabber.xml and restart the server. If you used the same port we did in the tag shown earlier, it should open port 5223 for SSL connections.You can use the netstat command to verify this: $ netstat -an|grep 522 tcp 0 0 127.0.0.1:5222 tcp 0 0 192.168.1.1:5223
0.0.0.0:* 0.0.0.0:*
LISTEN LISTEN
This shows the server listening for connections on port 5222 (the IP address 127.0.0.1 means that only local connections will be accepted) and port 5223 (on the network interface 192.168.1.1). Clients should be able to connect to the SSL port, carry out the SSL handshake, and send messages as normal. Let’s try it out with the WinJab instant messaging client. In the connection preferences, select the SSL port (5223, in this case) and select Use SSL Connection.Then connect as usual and the connection between the client and server is encrypted.
Figure 6.4 Configuring WinJab for SSL.
What’s involved in changing clients to connect with SSL? It varies depending on the language’s support for SSL. In Java using JabberBeans, it’s straightforward.When making the original connection, replace the ConnectionBean with a ConnectionBeanSSL.You just need to change code like this: ConnectionBean cb = new ConnectionBean(); cb.connect(InetAddress.getByName(server_host), 5222);
to this: ConnectionBean cb = new ConnectionBeanSSL(); cb.connect(InetAddress.getByName(server_host), 5223);
263
264
Jabber Security
That’s all the code changes. Now we need to add the certificate that we made for the server to the list of certificates trusted by the Java runtime environment. Unlike WinJab, the Java runtime won’t accept a certificate unless it’s signed by a trusted authority.The Java runtime includes several trusted certificates, but unless we get our certificate signed by one of them, we’ll have to manually add it to the list. Recall that the openssl tool created the certificate and private key, which we put together in the file key.pem.The certificate in there is delimited by ——-BEGIN CERTIFICATE——- and ——-END CERTIFICATE——-. Using a text editor, extract that section from key.pem into another file, called cert.pem. It should look something like this: ——-BEGIN CERTIFICATE——MIIC1jCCAj+gAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJVUzER MA8GA1UECBMIVmlyZ2luaWExEjAQBgNVBAcTCUFybGluZ3RvbjENMAsGA1UEChME U0FNUzESMBAGA1UEAxMJbXktamFiYmVyMB4XDTAyMTEyNzE2MjgxOVoXDTEyMTEy NDE2MjgxOVowVzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRIwEAYD VQQHEwlBcmxpbmd0b24xDTALBgNVBAoTBFNBTVMxEjAQBgNVBAMTCW15LWphYmJl cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyYw0TpeCc0ouRqr31NjLRBKq o35rHOLMY9cbySk0TsKK/SgHcH/twuZPf0HT98n2Zsgwndzqy6y72oKwuFKz3809 0kidOMIaCD4Zilfq27OJhJEjpjbI27xXnw4blaOJgaGc5C6/EkfEPGFct8LyPiaO uw2RMDtN5l+0TCGXOLMCAwEAAaOBsTCBrjAdBgNVHQ4EFgQU66D/K5DI11NZu6SE pByjVK5bSMYwfwYDVR0jBHgwdoAU66D/K5DI11NZu6SEpByjVK5bSMahW6RZMFcx CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTESMBAGA1UEBxMJQXJsaW5n dG9uMQ0wCwYDVQQKEwRTQU1TMRIwEAYDVQQDEwlteS1qYWJiZXKCAQAwDAYDVR0T BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBPkLgptz3A8GDYSdPCqfyZV7j/j7k+ v1idOdyRN+kLHLEB9KNyMcAoa5HdGyoiCClMOzafrd3a6vnhYJpef0D3hogmcEaU Gjj3qILY9TEpcsIFB/81fXPdZFUfRB2nczNGlNbAuqEzc4PA5DQuY7dvzcSQ9Ash lzH7ul3NqNA5yg== ——-END CERTIFICATE——-
Use the Java keytool utility to convert this certificate into a format that the Java runtime can understand.This command creates a Java keystore file that can be used as the set of trusted certificates: $ keytool -import -alias myjabber -trustcacerts -file cert.pem -keystore ➥myjabber.keystore Enter keystore password: 123456 Owner: CN=my-jabber, O=SAMS, L=Arlington, ST=Virginia, C=US Issuer: CN=my-jabber, O=SAMS, L=Arlington, ST=Virginia, C=US Serial number: 0 Valid from: Wed Nov 27 11:28:19 EST 2002 until: Sat Nov 24 11:28:19 EST 2012 Certificate fingerprints: MD5: C6:8B:25:E6:A9:D6:B7:90:21:EE:3C:FF:93:A1:9D:B3 SHA1: 9D:8E:7B:2D:B0:8C:8F:E7:73:74:F5:12:09:AB:9A:A0:D9:77:33:52 Trust this certificate? [no]: yes Certificate was added to keystore $
Server-to-Server Connection Authentication (Dialback)
The keystore password is arbitrary and must be at least six characters long. Because trusting a new certificate could be dangerous if done unwisely, the keytool utility prints the contents of the certificate so the user can make sure it looks correct. The only thing left is to tell the Java runtime to use the new keystore as its source for trusted certificates. A Java property accomplishes this. Add -Djava.net.ssl.trustStore to the command line. Here’s an example using the authentication tester you saw earlier: $ java -Djavax.net.ssl.trustStore=myjabber.keystore org.jabber.AuthPlain ➥my-jabber user1 user1 Work Successfully authenticated
SSL-encrypted client sessions make it much more difficult for a potential hacker to sniff passwords and other confidential information over the network. It’s important to remember that the data is still decrypted and vulnerable when it is at the client and at the server—SSL protects the data only while it is in transit. Services that connect using the mechanism that you saw earlier in this chapter connect using an unencrypted link. As of this writing, SSL is not supported for component-to-server connections or for server-to-server (s2s) connections.
Server-to-Server Connection Authentication (Dialback) So far you have seen how clients authenticate themselves to servers and how services authenticate themselves to servers.This section examines how servers authenticate themselves to each other. A server needs to talk to another server whenever a client needs to send a packet to a client or service that is resident on another server. For example, if user1@server1 sends a message to user2@server2, authentication needs to occur in three places, as shown in Figure 6.5. Are you really who you claim to be?
User1
Are you really who you claim to be?
server1
Are you really who you claim to be?
server2
User2
Figure 6.5 End-to-end authentication.
Servers authenticate clients by maintaining a database of authorized users. Although it would be possible to also require servers to maintain a database of all other servers with which they might communicate, that could be a heavy burden in working with Internet-scale numbers of servers. Fortunately, there already is a database of Internet hosts in the form of the Domain Name Service (DNS).The server-to-server (s2s) component uses DNS to validate hostnames to ensure that server connections are from the host they claim.This procedure is called dialback.
265
266
Jabber Security
The name dialback must come from an old mechanism used by dialup servers to make sure that only authorized users used them. If Alice wanted to start a session with the dialup server, she’d follow this procedure: 1. Alice calls the dialup service and says, “This is Alice, I’d like a session please.” 2. The dialup server hangs up on Alice. 3. The server looks up Alice’s phone number and calls her back. Because the server knows Alice’s phone number, no one else can impersonate her. (Unless they use her phone!) 4. Alice answers the phone and says, “I’ve been expecting your call.” 5. The session is established. s2s uses the same principle, but uses DNS as the phone book. Figure 6.6 illustrates the process. Receiving Server
Originating Server
DNS
Establish Connection Send Stream Header
Send Stream Header Send Dialback Key
Lookup IP Address of Originating Server Authoritative Server IP Address
Establish Connection Send Stream Header Send Stream Header Report Dialback Result Send Dialback Key Exchange Messages (if Successful)
Verify Dialback Key
Figure 6.6 Dialback.
Authoritative Server
Server-to-Server Connection Authentication (Dialback)
First, the originating server contacts the receiving server and establishes the connection with an exchange of stream headers.These headers use a specific XML namespace that distinguishes them from other connections.The stream header looks like this:
The receiving server responds with its own stream header:
The originating server then sends the dialback key to the receiving server.This is the key that the receiving server will use to verify the identity of the originating server. the dialback key
Now the receiving server looks up the IP address for OriginatingServer in DNS and opens a connection to that server.This is probably, but not necessarily, the same server as the one with which it already has a connection. It sends a stream header like this:
The authoritative server replies with its own stream header:
The receiving server can then ask the authoritative server to verify the dialback key with a packet like this: the dialback key
The id attribute matches the ID of the stream header that the receiver sent the originator when it first connected.The dialback key is the key that the originator provided. The authoritative server verifies the information (by whatever means it chooses) and, if the credentials match, replies to the receiving server with a packet with type valid and replies to the original ID like this:
267
268
Jabber Security
If the credentials don’t match, it responds like this:
If all goes well and the credentials match, the receiving server can inform the originating server with a packet like this:
and packets can flow across the connection. Fortunately, all of this is handled transparently by the s2s and dnsrv services. The newly-authenticated connection is left open for a while so that if more messages need to flow between the two servers, the dialback doesn’t have to be repeated.The length of time that the connection is held open is controlled in jabber.xml by the s2s service configuration parameter , which defaults to 900 seconds (15 minutes).This example section of the s2s configuration doubles that value to 30 minutes: ./libs/dialback.dll 1800
Although using the dialback protocol is a clever way of authenticating servers, it has some holes. First, it’s not that difficult to spoof DNS and hijack a hostname. Second, just because a host has an entry in DNS doesn’t mean that my server should accept messages from it. Finally, there is no provision in the 1.4.2 s2s component for SSL connections, so data flowing between servers is unencrypted.
Summary You’ve seen in this chapter that the security of the communications channels between parts of a Jabber system depends on several mechanisms. Figure 6.7 summarizes the Jabber security features covered in this chapter. The communications channels in Figure 6.7 fall into three categories as follows: Client-to-server (c2s). Authentication: JSM modules mod_auth_plain, mod_auth_digest, and mod_auth_0k. Confidentiality and Integrity: SSL. Server-to-service (accept and connect). Authentication: Shared secret hashed with session ID. Confidentiality and Integrity: none. Server-to-server (s2s). Authentication: Dialback based on DNS entry. Confidentiality and Integrity: none. n
n
n
Server-to-Server Connection Authentication (Dialback)
Client
External Services
External Services
Server
Server
Client
Figure 6.7 Jabber Communications Channels.
Although we expect that many of these shortcomings will be addressed in future versions of jabberd, implementers of Jabber-based systems are free to use other security measures, such as firewalls and Ipsec, to ensure the appropriate level of security for their environments.
269
II Jabber-Based Networked Applications 7
What’s in a Name:Web Services
8
Jabber and Conversational Software Agents
9
Jabber and System Control and Administration
10
Jabber and JXTA
11
Jabber Libraries for Popular Languages
7 What’s in a Name: Web Services
What’s in a name? that which we call a rose / By any other name would smell as sweet. Romeo and Juliet, Act II, Scene 2.
W
HEN IT COMES TO THE TOPIC OF WEB services, the Bard was right. If we practice reductionist logic, many people are attempting to gain mind- and marketshare by putting their own spin on the concept of distributing logic over a network. Of course, each purveyor of middleware suites - some free and open source, some very expensive and closed or proprietary- claims to add some special value. Some add to their approach (service discovery methods, service naming schemes, language interoperability, transactional quality of service, and so on), and in some cases these value-adds are actually useful, or at least deliver something close to the value of their hype. Interestingly enough, we have had Web services in some form since shortly after computing stepped out of its “primordial ooze” phase—since at least the advent of relational database servers and client applications.We just didn’t have quite the flexibility of deployment and applications that we do now.What’s changed, of course, is the theme that’s at the very heart of this book—we now have XML messaging running between parts of an application.
First-Generation Applications: Servers and Glass Terminals Initially, as depicted in Figure 7.1, the locus of the application was central and generally not available beyond the hardwired connection between a timesharing terminal and a mainframe.The essential character of applications in this era was data entry within the organization against a mainframe-resident data base. Until the development of the ARPANET and later the Internet, external access was not possible and the language of the conversation from the hardwired terminal and the mainframe was, by and large,
274
What’s in a Name: Web Services
structured query language (SQL), and the response from the mainframe was a resultset or a status message.The semantics of the conversation were fairly fixed, as was the syntax, and the transport mechanism was bisynchronous and byte-oriented.The common gear of the day for a client was a 3270 glass terminal. Ironically, the emergence of the World Wide Web and browsers would turn all those expensive PCs we bought in the 1990s back into glass terminals again, as you’ll see a couple sections from now.
Figure 7.1 3270 terminals and mainframes.
Second-Generation Applications: Servers and Clients Somewhat later the “hard wired” constraint was relaxed owing to the development of network protocols such as TCP (Transmission Control Protocol) and IP (Internet Protocol).The first distributed business-style client-server applications were built by splitting the application locus into two parts.Thus, a client application, by this time running on a PC, could now be remote from the server application (Figure 7.2.) and transmit and receive asynchronously. In the parlance of the ”Model-View-Controller” paradigm, the database server held the model, the client was responsible for generating the view, and both shared implementation of the controller.
Figure 7.2 Initial networking of applications.
A variation of basic client-server design was the introduction of the so-called ”three-tier architecture,” which placed an application server next to the database server. Now the locus of the application moved so that the client held only the view of the data.The application server held the controller logic, and the database server, the data model (Figure 7.3).
Third Generation Applications: Enter the Web
Figure 7.3 AppServer plus DBServer.
The three-tier architecture started out fairly database-centric, but during the 1990s grew to encompass many other types of applications. On the plus side, an organization could buy “heavy iron” and improve the query time to a back-end server (such as a database server), could host more sophisticated applications than the client PC might be capable of doing, and could manage with fairly slow (9600 BAUD) modem connections. On the down side, the client-resident view component still needed custom development (usually in a nonportable GUI toolkit, such as the MFC, or Microsoft Foundation Classes).This bound applications to specific platforms and denied the benefits of the application from other platforms.
Third Generation Applications: Enter the Web With the mid-1990s emergence of the World Wide Web, applications could now take advantage of a uniform and ubiquitously deployed operating system feature (the Web browser) as a generic container for the view portion of a distributed application. In addition to giving the Internet a de facto look and feel, the Web influenced the operational characteristics of almost all distributed applications (see Figure 7.4).
Figure 7.4 The Internet generation.
275
276
What’s in a Name: Web Services
The Internet now emerged as a viable, secure, transaction-safe network for business communications. Although early Web architectures allowed Web browsers to access databases directly, generally through the Common Gateway Interface (CGI) via HTTP, the receiving end of the HTTP request was (and in some cases still is) a PERL program that picked apart the string data being passed in the body of the HTTP request.The structure of the data is generally defined by the requirements of the servlet or similar program on the “server side” of the gateway. Although this removed some of the complexity from the client side of the equation by making every client user experience similar to that of using a 3270 terminal, it also removed some capabilities from the mix, notably the client’s ability to maintain stateful information, which is information about the ongoing session between the parts of the application. Additionally, it forced all applications to look pretty much the same, and all user experiences to resemble filling in paper forms. The possible upside was that at least the form you submitted was processed pretty much instantly.Web application servers brought increased performance and scalability, as well as enabling far more sophisticated business logic. The most important difference between the newly emerged Web model and the earlier client-server paradigm was that applications no longer needed dedicated connections between the parts of the applications.Web applications were designed to communicate “through the network cloud,” that is, over the Internet—the ubiquitous public network based on simple agreed-upon standards like Hypertext markup language (HTML) and hypertext transfer protocol over IP (HTTP). Ironically, after the emergence of the Web, two rather interesting things happened. First, connections between clients and servers no longer needed the participation of the telephone companies to provide banks of dedicated phones at the server point of presence. So, the overall valuation of the telecommunications industry dove to lows in inverse proportion to the valuation of any company that stuck a Web server in front of its service (whether the service actually had value or not). Secondly, even clients located on the same network as a server (for example, in a corporate intranet) now tended to use HTTP for transport, a browser client, and a server.The model worked well for any distributed application regardless of where the parts were located. Really, though, the simple agreement over the carrier protocol (HTTP) and how to display something in a browser (HTML) pretty much describes the value that the Web offers.There’s no agreement on how to find or describe a service, no standards on how to invoke a service’s methods or what data gets returned.To resolve those kinds of issues, we need to look beyond the simple Web sever model.
Fourth-Generation Applications: XML and Web Services With the advent of the Web services epoch, a new set of acronyms (XML-RPC, SOAP, WSDL, UDDI) are a part of the vocabulary of Web services. Among the claims for Web
Fourth-Generation Applications: XML and Web Services
services is that they are great enablers for business-to-business (B2B) automated interactions. Proponents suggest that they enable businesses to Improve collaboration with customers, partners, and suppliers by reducing integration time and expense compared to custom-built B2B and P2P (person-to-person) solutions Increase revenue via expanded distribution channels, provide quicker time-tomarket for new value-added services, and enable public discovery of existing assets Enhance customer service levels by allowing customers and trading partner access to core systems n
n
n
n
n
n
Generate new revenue opportunities through creation of private trading networks or ecosystems Respond quickly to changing market conditions and customer preferences by utilizing loosely coupled, modular services Protect investments and avoid technology lock-in via standards-based architecture and hardware, operating system, and programming language neutrality
Surely if you’ve been plugging away at this book, at least a few of these arguments (the technical arguments, if not the business justifications) will sound strikingly familiar to you, as they are the identical arguments for creating distributed systems with Jabber. If we were to show the protocol stack for this emerging Web services architecture, it would resemble Figure 7.5, although, as we will explain shortly, this is not quite accurate, and indeed as of the publication date of this book, large software vendors are busily trying to define, redefine, and create proprietary elements of this layer cake to suit their various goals of creating software dominance and lock-in. Our biases and natural skepticism tend to make us favor “free and open” over “expensive and proprietary.”We therefore tend to lean toward concepts such as Jabber and away from concepts that appear at first blush to be open, but may actually be battlegrounds for capture and control of yet another piece of the infosphere. Eventually, you may be called upon to render your technical justification for preferring Jabber to creating “Web-service-like” architectures. At such times, you might want to suggest that although your organization can certainly spend many thousands of dollars on middleware to facilitate a distributed service architecture, it can also choose to embrace Jabber, essentially for free. That is not to say that there are not some free and open APIs at various levels of the stack shown in Figure 7.5; there are, as we will illustrate in code examples.The real question software artists should ask is, “Who controls the direction, destiny, implementation, and utility of the various layers of the protocol stack?” If it’s not the open source community, then even the best APIs are simply decorations on a cake owned by someone else, a cake for which your organization may have to pay dearly for a slice.
277
278
What’s in a Name: Web Services
Figure 7.5 Web service protocol stack.
Let’s look at how Web services are similar to and different from the service architecture, addressing scheme, and service discovery in Jabber. In Chapter 10, “Jabber and JXTA,” we will do a similar dissection of JXTA and Jabber. Later, we’ll show some examples of interoperating with these layers. The most basic transport layers, HTTP over TCP/IP, need no introduction. If you are reading this book, we assume your mastery of them both conceptually and in writing code.
XML-RPC XML-based Remote Procedure Call, or XML-RPC, is at the bottom of this rather tall and heavy Web services protocol stack, directly above the Transport layer. An XML-RPC message is an HTTP-POST request in which the body of the request is encoded as XML. When a procedure is executed on the remote peer, the value it returns is also formatted in XML. Does this sound familiar to you, the Jabber-savvy developer? Procedure parameters can be scalars, numbers, strings, dates, or other primitive objects. Additionally, they can also be complex objects and list structures. Understanding the data characteristics accompanying the call and return are the responsibility of the peers involved in the conversation.This is very much like what happens in an interaction between Jabber clients, one or both of which may be systems and not humans. In the Python example shown here, an XML-RPC client is shown. Later, we’ll modify it to include Jabber capabilities. Listing 7.1 reallySimpleXMLRPCInvoker.py 1:import xmlrpclib 2:aPeer = xmlrpclib.Server(‘http://localhost:8080’) 3:print xmlrpclib.dumps(tuple([“There was a young lady of Nantes”]), ‘aServer.Rot13’) 4:print aPeer.Rot13(“There was a young lady of Nantes”)
The output from running the client from the command line looks like Figure 7.6.
Fourth-Generation Applications: XML and Web Services
Figure 7.6 Output from reallySimpleXMLRPCInvoker.
Just as a debug device lets you see what got transported over the wire, we printed on line 4 the result of the call to dumps() whose Python signature is dumps(params,methodname=None, methodresponse=None, encoding=None). It converts an argument tuple (remember that in Python, a tuple is a read-only list) to an XML representation of the data needed to perform an XML-RPC request (or response, if the methodresponse option is used). It’s interesting simply as a learning device here, but will become slightly more useful when we mix XML-RPC with Jabber. The transport mechanics of the protocol are simple, as illustrated by Figure 7.7. One side of the conversation acts as a simple HTTP server, fielding HTTP/POSTs from any caller able to reach it by IP address (more about this shortly.)
Method name and parameters encoded as XML HTTP XML
HTTP XML Method
MethodCall
Method
invoker
invoked Results encoded ads XML Figure 7.7 P2P over XML-RPC.
279
280
What’s in a Name: Web Services
The method call plus its consumed arguments are encoded in XML.The invoker establishes an HTTP connection with the invokee.We are purposely choosing not to use the words “client” and “server,” terms that sometimes conjure up levels of meaning that are not intended. Here, we simply want you to think in terms of two peers conversing, using HTTP as the bearer channel, and XML syntax to carry the semantics of the dialogue. In this example, the invoker simply opens a channel on port 8080 at localhost, and then invokes Rot13, hosted by a peer offering to respond on that URI, passing that method a string value.The server’s Rot13 method shifts each alpha character in the input stream by 13 letters (thus 1: And now for something completely different becomes 1: Naq abj sbe fbzrguvat pbzcyrgryl qvssrerag) and returns the convolved string to the invoker. Obviously, we’ve contrived the example such that both parties in the conversation are in agreement about where the server is (http://localhost:8080), the name of the method on the server (Rot13), and what sort of input argument it consumes (a string). If we hadn’t known all this a priori, the conversation between the parties would have failed. Here’s the other side of the conversation, the XML-RPC service being invoked. It is equally simple. Listing 7.2 reallySimpleXMLRPCInvokee.py 1:import SimpleXMLRPCServer 2:import xmlrpclib 3:class ReallySimpleXMLRPCService: 4: def Rot13(self, text): 5: rot= “” 6: for x in range(len(text)): 7: byte = ord(text[x]) 8: cap = (byte & 32) 9: byte = (byte & (~cap)) 10: if (byte >= ord(‘A’)) and (byte
As of this writing, however, there is not a serious JEP to do such a thing. Again, you can think in terms (for the time being) of using subelements.
UDDI Universal Description, Discovery, and Integration (UDDI) really isn’t the top of the Web services protocol stack (as Figure 7.5 suggests), but rather a service discovery system that might be used with WSDL, SOAP, and other implementations of remote procedure calling. UDDI provides a searchable set of services, offering equivalents to telephone white pages and yellow pages, as well as more technical “green pages.” Originally conceived as a centralized repository for all the world’s services, it has largely become decentralized and made proprietary as organizations create strategies to control the path to information discovery on the Internet.There are no JEPs under consideration currently to use UDDI for Jabber service location or conversely, to transport UDDI requests, say in stanzas. If it appears that we are “waffling” by not presenting code examples that would demonstrate potential workarounds either for the WSDL or UDDI, remember that both are still in relatively early phases of development.We can’t accurately gauge their ultimate direction; IBM and Microsoft both have patent claims on WSDL, and UDDI is managed by a much more closed consortium than the W3C. So although it’s certainly possible (maybe even useful in some cases) to build a Jabber system that tunnels WSDL and UDDI documents in Jabber packets, the whole point of Web services is interoperability, and without standards, interoperability is limited to one’s sphere of control.
285
286
What’s in a Name: Web Services
Jabber and XML-RPC We spent several pages discussing the evolution of distributed applications and then describing Web services in general, and now you are probably wondering how Jabber can enable and augment Web services.We have described how the Web services transport mechanisms (XML-RPC and SOAP) make it possible for applications divided by a network link to work together, how UDDI makes it possible for networked participants to discover one another’s existence, and how WSDL makes it possible for the participants to arrive at the semantics (that is, the arguments and their types) for conversation. It may have occurred to you that all this Web services complexity has simpler analogues in Jabber, and if it has, indeed we concur with you.We have shown in preceding chapters how Jabber works as a distributed service architecture, extending XML to encode data, transporting application-to-application, person-to-application, and systemto-system conversations, even through firewalls. Jabber also has a service discovery mechanism for components and other clients acting as services (“servants”). Why then should Jabber architects and developers concern ourselves with working in concert with other mechanisms, such as the Web services protocol stack (which is quickly becoming closed and proprietary), or JXTA (open source but still experimental and evolving)? The short and simple answer is, “Because they’re there,” and because they (Web services, at least) are receiving and will continue to receive significant corporate largess from very well-funded (gigantic, even) software companies whose interests are deeply rooted in assuring that they capture and control as much of the distributed services market as possible. Although Jabber can do much of what is done in the Web services stack, Jabber as an open sourced resource does not fit a proprietary economic model. It can’t be easily closed and monetized by a giant software company. Of course, Jabber can still do something that they ordinarily can’t do, and that is to solve the firewall problem. As Figure 7.9 depicts, an invoking peer is on one side of a firewall.The invoker would like to use code such as shown in Listing 7.1 to make use of the Rot13 service. Now, however, imagine that the service is on the other side of a firewall. An HTTP-POST to a non-routable IP address will not be able to get through the firewall; thus, the call will fail.We will show code for a couple ways around this problem. In both cases, the invoker will be a simple Jabber peer connecting through a Jabber switch on the open internet. One version of the invokee (invoked peer) will show bundling the remote method capability directly into the peer. In the other, an improved version shows a Jabber client acting as a service relay for the same HTTP-based service we show in Listing 7.2, which remains unchanged but can now exist inside a firewall.
Jabber-based RPC
invoker
invoked HTTP
JABBER
ON
F I R E W A L L
HTTP
Off JABBER Switch
ServerProxy
Figure 7.9 Jabber acting as a service proxy for nonroutable IP addresses.
Jabber-based RPC What we’re going to show you is based on the fact that Jabber messaging uses an alternative (to straight IP) address space and routes via a server. Remember that the XMLRPC specification defines an encoding of RPC requests and responses—a method call, the arguments it consumes, and the result it returns.The specification also mandates HTTP as a transport. A Jabber-RPC strategy would replace HTTP as the transport, which as we’ve said makes it possible to overcome firewall and NAT-related problems traditionally associated with HTTP port traffic.There’s at least one implementation that does this directly (in PERL, for example, the Jabber::RPC module does this), but we’re going to show you how to piece together a simple one of your own.
Jabber RPC Invoker In Listing 7.1, a Jabber client connects to a Jabber switch. For convenience of demonstration, we are connecting through a localhost using the name peer-a.The peer-a application is designed to connect to a Jabber endpoint peer-b.What’s a little different from the norm here is that there is no mutual roster exchange required.That is, peer-a and peerb are not going to be rigged to exchange messages; rather what will happen is that the data encoded for the RPC will be wrapped in an packet. Because the Jabber switch will not prevent the flow of infoqueries, delivery is assured to the other side.
287
288
What’s in a Name: Web Services
Listing 7.6 JabberRPCPeerA.py: Invoking 1:import jabber 2:import xmlrpclib 3:import string 4:import sys 5: 6:Server = ‘localhost’ 7:Username = ‘peer-a’ 8:Password = ‘peer-a’ 9:Resource = ‘rpc’ 10: Endpoint = ‘peer-b@localhost/rpc’ 11: Method = ‘Rot13’ 12: 13:text = “There was a young lady of Nantes” 14:request = xmlrpclib.dumps(((text),), methodname=Method) 15:#print request 16:con = jabber.Client(host=Server) 17:try: 18: con.connect() 19:except IOError, e: 20: print “Unable to connect: %s” % e 21: sys.exit(0) 22: 23:con.auth(Username, Password, Resource) 24: 25:iq = jabber.Iq(to=Endpoint, type=’set’) 26:iq.setQuery(‘jabber:iq:rpc’) 27:iq.setQueryPayload(request) 28: 29:result = con.SendAndWaitForResponse(iq) 30:#print “Done! Result:”, result 31: 32:if result.getType() == ‘result’: 33: response = str(result.getQueryPayload()) 34: parms, func = xmlrpclib.loads(response) 35: print parms[0] 36:else: 37: print “Error” 38: 39:con.disconnect()
The calling parameters are set up on lines 6–12. Note the import of xmlrpclib on line 2, which has methods for bundling and unbundling parametric data as well as handling the actual XML-RPC protocol itself. Line 14: 14:request = xmlrpclib.dumps(((text),), methodname=Method)
Jabber-based RPC
uses the dumps method to convert the argument that (in this Python library) must be converted from a Python tuple to a properly encoded XML-RPC request (or response, if the methodresponse optional argument is used). If you want to see the XML created in the request, uncomment line 15.The encoding was shown previously in Listing 7.3. Lines 16–23 log the user into the switch (substitute any valid user). Next, the critical part: Create a user peer-b on your Jabber switch. For simplicity we are using localhost. Assign the resource rpc to the username. Create an infoquery packet (line 25) addressed to peer-b@localhost/rpc. Next, set the query itself to a remote procedure call, then set the payload to the XML stanza you created (lines 26–27). 25:iq = jabber.Iq(to=Endpoint, type=’set’) 26:iq.setQuery(‘jabber:iq:rpc’) 27:iq.setQueryPayload(request) 28: 29:result = con.SendAndWaitForResponse(iq)
Finally, make the call and wait synchronously for the response.The returned response object contains the return type and an encoded payload. We test the result type to determine whether we got a good response or a “fault” (that is, an error), get the payload, and then load the returned stanza (shown earlier in Listing 7.3) into a Python structure using loads() (lines 32–35).The parms structure is an array, but normally only the zero-th slot is filled. 32:if result.getType() == ‘result’: 33: response = str(result.getQueryPayload()) 34: parms, func = xmlrpclib.loads(response) 35: print parms[0]
Jabber RPC Service Provider (First Version) Listing 7.7 looks at code that will respond to the invoker we just wrote. It bundles the invoked function into its code body. Listing 7.7 JabberRPCPeerB.py: Bundling the Remote Method with a Jabber Peer 1:import xmlrpclib 2:import jabber 3:import sys 4:import string 5:import sys, re, os, string 9:Server = ‘localhost’ 10:Username = ‘peer-b’ 11:Password = ‘peer-b’
289
290
What’s in a Name: Web Services
Listing 7.7 Continued 12:Resource = ‘rpc’ 16:# Global procedures: 17:def Rot13(text): 18: rot= “” 19: for x in range(len(text)): 20: byte = ord(text[x]) 21: cap = (byte & 32) 22: byte = (byte & (~cap)) 23: if (byte >= ord(‘A’)) and (byte \n”, payload 41: payload = xmlrpclib.loads(str(payload)) 42: #print “Payload.loads()—>\n”, payload 45: if query_ns == jabber.NS_RPC: 46: resultIq = jabber.Iq(to=iq.getFrom(), type=’result’) 47: resultIq.setID(iq.getID()) 48: resultIq.setFrom(iq.getTo()) 49: query_result = resultIq.setQuery(iq.getQuery()) 50: resultIq.setQuery(jabber.NS_RPC) 51: evalString = payload[1]+”(‘“+payload[0][0]+”’)” 52: returnParams = xmlrpclib.dumps(tuple([eval(evalString)])) 53: print “returnParams==>\n”, returnParams 54: resultIq.setQueryPayload(returnParams) 55: con.send(resultIq) 56: 57: 58:def presenceCB(con, pres): 59: “Got PresenceCB” 60: pass 61:
Jabber-based RPC
Listing 7.7 Continued 62:def messageCB(con, msg): 63: “Got messageCB” 64: pass 65: 66:#————- “MAIN” ——————————————67: 68: 69:con = jabber.Client(host=Server) 70: 71:try: 72: con.connect() 73:except IOError, e: 74: print “Couldn’t connect: %s” % e 75: sys.exit(0) 76:else: 77: print “Connected” 78: 79:con.process(1) 80: 81:if con.auth(Username, Password, Resource): 82: print “Authorised” 83:else: 84: print “problems with handshake: “, con.lastErr, con.lastErrCode 85: sys.exit(1) 86: 87:con.setMessageHandler(messageCB) 88:con.setPresenceHandler(presenceCB) 89:con.setIqHandler(iqCB) 90:WAITSECONDS = 300 91: try: 92: while(1): 93: con.process(WAITSECONDS) 94: except KeyboardInterrupt: 95: con.disconnect() 96:
First, we set up an identity for the service.This can be anything—just make certain it’s the same target as line 10, Listing 7.6. Lines 17–27 are the Rot13 method shown in
291
292
What’s in a Name: Web Services
Listing 7.2. Neither the presence or message handler callbacks (lines 58–64) are particularly interesting; they are not involved in the scenario.We extract the query’s namespace on line 37 and get the payload on line 39. Uncomment the print lines if you want to see what the payload looks like as encoded XML and then as a Python structure. We check the query type and if it’s appropriate we start formulating a reply. Much of this kind of response formation you’ve already seen. Notice that we set the type to result, which is what the invoker expects. On line 51, we construct a string from the unpacked XML and use the string in an eval to invoke the Rot13 method (dynamic languages are fun, aren’t they!). On line 52, we get a result from the method, create a Python tuple from it, and use dumps() to form up the return XML. Uncomment line 53 if you want to examine them. Finally, we set the payload and return it to the invoker. The driver for the process begins on line 69, where we create a connection object, handshake with the switch, and set the handlers and process in a periodic loop, interruptible by a Ctrl+C. To try this out, simply open a pair of command windows and run each application. NOTE You might be wondering why we created a client rather than a component in the previous example. In fact, you could have constructed the service provider as either a component or a client. The difference for this exercise is immaterial. Just remember that components take a bit more setup in the configuration of your jabber.xml. For ease of demonstration, a client is rather easier.
Jabber RPC Service Provider (Second Version) Looking back at Figure 7.9, you will notice that we completed only the first leg of the journey.We rolled the service from Listing 7.2 right into the Jabber endpoint.What if, instead, you wanted to keep the original responder as is, (that is, an XML-RPC service with an HTTP transport)? You might want to do this for a variety of reasons: n
n
n
n
Tunneling one or multiple firewalls. (Figure 7.9 shows only a single firewall, but there’s no reason why both Jabber clients might not be behind firewalls.) This becomes a very difficult situation in the HTTP world, as you may be aware. (This is the most obvious reason for refactoring the application in this manner, but also consider the other motivations, as well.) Keeping a single code base for the remote method itself (instead of having to support it in two places, the Jabber-RPC version and the original XML-RPC version). Maintaining the original interface for Web-based users. Providing multiple access methods.
The easiest way to accomplish separation of functional concerns is to keep the service provider just as it was in Listing 7.2 and put a Jabber client inside the firewall with it.
Jabber-based RPC
The Jabber client acts as a service proxy for the service provider trapped inside the firewall, and although we don’t show this aspect, we can do user authentication on behalf of the service provider. Listing 7.8 JabberRPCPeerC.py: Using Service Proxy 1:import xmlrpclib 2:import jabber 3:import sys, re, os, string 4: 5:Server = ‘localhost’ 6:Username = ‘peer-b’ 7:Password = ‘peer-b’ 8:Resource = ‘rpc’ 9: 10:ServiceEndpoint = “http://localhost:8080” 11: 12:# Global procedures: 13: 14:def iqCB(con, iq): 15: query_ns = iq.getQuery() 16: 17: if query_ns == jabber.NS_RPC: 18: payload = iq.getQueryPayload() 19: #print “Payload—>\n”, payload 20: payload = xmlrpclib.loads(str(payload)) 21: #print “Payload.loads()—>\n”, payload 22: rotServer = xmlrpclib.ServerProxy(ServiceEndpoint) 23: payload = payload[0][0] 24: #print “payload==>”,str(payload) 25: rotText = rotServer.Rot13(payload) 26: resultIq = jabber.Iq(to=iq.getFrom(), type=’result’) 27: resultIq.setID(iq.getID()) 28: resultIq.setFrom(iq.getTo()) 29: resultIq.setQuery(jabber.NS_RPC) 30: 31: rotText = xmlrpclib.dumps(tuple([rotText])) 32: resultIq.setQueryPayload(rotText) 33: 34: con.send(resultIq) 35: 36: 37:def presenceCB(con, pres): 38: pass 39: 40:def messageCB(con, msg): 41: pass
293
294
What’s in a Name: Web Services
Listing 7.8 Continued 42: 43:#————- “MAIN” ——————————————44: 45:con = jabber.Client(host=Server) 46: 47:try: 48: con.connect() 49:except IOError, e: 50: print “Couldn’t connect: %s” % e 51: sys.exit(0) 52:else: 53: print “Connected” 54: 55:con.process(1) 56: 57:if con.auth(Username, Password, Resource): 58: print “Authorised” 59:else: 60: print “problems with handshake: “, con.lastErr, con.lastErrCode 61: sys.exit(1) 62: 63:con.setMessageHandler(messageCB) 64:con.setPresenceHandler(presenceCB) 65:con.setIqHandler(iqCB) 66: 67:WAITSECONDS = 300 68:try: 69: while(1): 70: con.process(WAITSECONDS) 71: 72:except KeyboardInterrupt: 73: con.disconnect() 74: os.exit(0)
Let’s look at a simple case in Listing 7.8.To test this code, you should start up the reallySimpleRPCServer from the command line so that there will be a running target for your client. In this client, just as before, we provide a very minimal callback for message types other than infoqueries (lines 37–41).The “main logic” (lines 45–73) is identical to the old version.The big change is in the infoquery callback. Again, we check for the namespace of the query, and if appropriate, unbundle it as we did previously.
Jabber-based RPC
If you uncomment the print statement on line 19, you get the actual XML payload: Rot13 There was a young lady of Nantes
This is not quite what we want to pass to the remote XML-RPC, though, at least not in the Python implementation used in the example. It actually wants to see the raw argument to the remote method, so we have to go through the pain of converting from XML to an internal data structure.That’s why we do what you see on line 20: 20:
payload = xmlrpclib.loads(str(payload))
If you uncomment the next print line, you will see the internal data structure, a list of items, the first of which is a list (the potential argument list for the remote method): ((‘There was a young lady of Nantes’,), u’Rot13’)
Of course, a proper implementation would do a lot more grooming of the payload, but because we know exactly what the remote method requires, we take only the first member of the first list slot (the string “There was…”). After making the method call (line 25), we set up the return packet and send it off. Try this out, and you should see the resulting Rot13’d string echoed on the command line. If you run the server in debug mode and observe the traffic, you will notice the same payload shown in Listing 7.3. However, because the payload is being transported via Jabber rather than HTTP, it’s wrapped in the outer Jabber envelope, shown in bold print: Listing 7.9 XML-RPC Wrapped for Jabber Transport Rot13 There was a young lady of Nantes
295
296
What’s in a Name: Web Services
Jabber-RPC Object Lessons XML-RPC is implemented to target a domain or IP address, and reference implementations of SOAP have this limitation as well.We showed a simple way around this by bridging XML-RPC over Jabber. The object lesson here is that you can use Jabber to transport XML-RPC and SOAP messages. Depending on the library implementation, you may have to do more or less work. Instead of using normal DNS or IP addressing, calls and results are routed between Jabber entities by JID. Distributed applications, including access to a personal desktop behind a firewall and groupware applications, can reach any Jabber-enabled desktop. People using Jabber as their projection in cyberspace can move around without losing connectivity to their applications. It is noteworthy to mention that the Jabber Software Foundation moved “JEP-0009:Transporting XML-RPC over Jabber” final status in late 2002, making it the transport of choice for XML-RPC traffic over Jabber. (See http:// www.jabber.org/jeps/jep-0009.html for details.) Note For the ultra-curious out there, we can foresee a flood of emails asking for the completion of the limerick used in our Rot13 example. There may be several versions, but the two that we admit to knowing are: There was a young lady of Nantes Who said to her boyfriend, “You can’t!” But he was a stickler For usage particular And said, “Yes I could, but I shan’t.” And: There was a young lady of Nantes, Who lived with a miserly aunt; When asked to a ball, Said: ‘I’ve no clothes at all. I must borrow the plumes of my tante.’ So now you know.
Summary What we learned in this chapter is that there is an emerging (though hardly mature) stack of Web services that seek to structure distributed computing in a way that is most congenial to B2B users and to their own commercial agendas.We in the conversational software community should understand that in the short term, the evolution of languages and systems is likely to produce all sorts of curiosities and dead ends.We can embrace and coexist with these turns just as Cro-Magnon may have coexisted with
Summary
Neanderthal, but that does not mean that we need to believe that certain developments in computing represent any sort of evolutionary end game. Just looking at the evolution of architectures depicted in Figures 7.1–7.4 illustrates how many different approaches to achieving distribution there have been, none of which have proven to be a “final form.” Are Web services likely to be a main evolutionary branch or simply another iteration in the evolution of “systems of systems?”We believe the answer is that they are the latter, and that conversational systems based on Jabber and its descendants are likely to be the wave of the future. It’s our belief that future systems will indeed be based on a conversational, messagebased transport architecture (like Jabber), because all network endpoints will look like loosely coupled, highly autonomous, distributed computational entities with conversation-like syntactic/semantic capabilities.We believe that in using the techniques and philosophies of this book, you will be working with and adding to the body of knowledge leading to the continued productive evolution of systems. In the next chapter, we go a little far out into suggesting a current shaping for “conversational systems” by applying a little artificial intelligence research that you can easily incorporate into your Jabber-based applications.We think it’s one of the more fun diversions you will want to try, and to that end, let’s move on to Chapter 8 and the topic of “Jabber and Conversational Software Agents.”
297
8 Jabber and Conversational Software Agents
There is no such thing as conversation. It is an illusion. There are intersecting monologues, that is all. Rebecca West What a delightful thing is the conversation of specialists! One understands absolutely nothing and it’s charming. Edgar Degas Recreation, not instruction, is the aim of conversation. Oscar Wilde
T
HIS NEXT PROJECT ENABLES HUMAN CLIENTS to talk to no one (literally), and whether the objective is a simple recreational application or a portal to specialized services, you will be able to build a conversational agent that serves a number of simultaneous users and sometimes returns intelligent answers to questions.Whether or not the answers that your applications return to users will appear sensible is largely a matter of whether you have constructed an intelligent dialogue system in something called AIML (Artificial Intelligence Markup Language), which we’ll describe in a bit.The easier part will be using a Jabber backbone to create the application.We say “easier” rather than “easy” because two servers are involved with this application—your familiar Jabber server and something called an Alicebot server—which converse with users.There will be a bit of
300
Chapter 8
Jabber and Conversational Software Agents
work building a conversational multiplexor into the Alicebot server, but stick with us. We’ll walk you through and have your smart conversational agent up and talking to users in no time.
Motivating This Application First of all, why would you want to build a conversational agent? There are many reasons, we think. Let’s say that you want to build a command interface to an application. It makes no difference whether the application is distributed or local, but your framework should accommodate either.Working out the command structure and forcing the user to remember the arcane language of the command-line interface (as we did in the inventory update application in Chapter 5, “Extending the Jabber Server,” for example) is a pain; not to you as the application designer perhaps, but certainly to the user. It would be better if a user could talk to the command-based application in a way that was more like a natural conversation in a natural language.That’s one goal (of many) of AIML and the Alice open source project. Further, because the model of instant messaging has accustomed us to deferred responses from the entities (usually humans) on the other end of a conversation, applications are transitioning from using simple tools, such as spreadsheets where the response to the user must be relatively instantaneous, to using potentially complex services, where the system response can come at any time and alert the user when the response is ready.We as developers should be getting acquainted, at least, with building this style of application.
Accomplishing the Objective As we’ve suggested in various spots in this book, Jabber applications may be components or they may be clients. In this chapter, we build a client-style application that acts as an intermediary to an external service.The application looks a bit weird, because the client that’s built is actually a multiplexor compiled into the Java implementation of the Alicebot server itself. This will be a busy chapter, because we introduce a fairly complex and probably unfamiliar technology. Ironically, as we already said, the easy part will be to create the Jabber client portion.
What Is Alice? Alice is, simply put, a “chatterbot”—a software entity that can respond to your typed-in conversation.The degree to which chatterbots like Alice can hold intelligent conversation is the topic of much discussion and debate, but for our purpose we will state that, given a sufficiently constrained topical area, that Alice seems intelligent enough. Alice
Alice Design Principles
supplies responses to your input using its own built-in parser and canned responses, or by executing bits of ECMAScript (JavaScript) to mine external resources.This latter capability enables Alicebots to expand their repertoire of knowledgeable responses. Essentially, the Java versions (available at www.alicebot.org) of the Alice Server embed the Rhino ECMAScript interpreter. Rhino enables you to invoke any Java class. ECMAScript enables you to interact by using HTTP or sockets to connect with external resources.
Why Alice Appears to Work A conversation with an Alicebot can (depending on emergent properties of the discourse) appear to reveal a fair amount of intelligence on the part of the virtual participant (if not the human). According to Dr. Richard Wallace, Alice’s innovator, there is a fundamental reason for this, which has to do with the nature of conversational interaction in a human language. He says, “Considering the vast size of the set of things people could say that are grammatically correct or semantically meaningful, the number of things people actually do say is surprisingly small.” In short, people don’t say a lot of original things or phrase them in unusual ways.This gives you as a developer a reasonable assurance that people using your AI system will try to say sensible things, and that your code can yield believable responses.
Alice Design Principles The Alicebot server shares a few design principles with the Jabber server. For one thing, it gains its startup knowledge from an XML-based database called startup.xml (normally found in /conf). Also, like Jabber, it allows plugin of additional capability.We will use this to plug in our own custom Jabber transport so that any number of humans (or other applications) can query Alice. Like Jabber, Alice has core components, and to add a new core component, you will have to compile it into the server. Alice already has a built-in conversational interpreter, a message callback mechanism, support for several kinds of GUI clients, and a multi-client multiplexor. If you haven’t run into the term multiplexor before, here’s how the idea is explained by Dr. Richard Wallace, Alice’s inventor: The Multiplexor controls a short “carnival ride” for each user.The Multiplexor puts the client in his/her seat, hands him/her an ID card, and closes the door.The client gets one “turn of the crank.” He/she enters his/her ID, multiline query, and then receives the reply.The door opens, the Multiplexor ushers him/her out, and seats the next client.”[1]
1 To gain more insight into the entire Alice philosophy and effort, make sure to visit www. alicebots.org, but be warned you may be sucked into a whole new, deep, and potentially mind-expanding realm.
301
302
Chapter 8
Jabber and Conversational Software Agents
The Alicebot Server To use or extend Alicebot, you should download the server package from Alicebot.org. (See “Resources” in Appendix C.) Specifically for this chapter, you should download Program D (the Java implementation) from http://www.alicebot.org/downloads/ and the associated tools.When you download and install on Windows, your installation should resemble Figure 8.1.
Figure 8.1 Alice’s installed files.
The following sections highlight some of the more interesting folders installed with the Java Alice Server.
AIML Folder—Alice’s Built-in Knowledge You should see an AIML folder that is the root folder for the standard knowledge base shipped with Alice. Each of the snips of knowledge is contained in an .aiml file. AIML files are simply an XML dialect understood by the Alice Server.You can extend the knowledge base by adding folders containing .aiml files; as long as these are mentioned in the appropriate stanzas in the startup.xml configuration file (see “Alice Configuration Files,” later in this chapter), they will be added to the knowledge base. Tip To learn AIML in deep detail, see Appendix C for resources. We will cover enough AIML in shallow detail here so that you will be able to get a good start on defining some custom knowledge on your own.
The Alicebot Server
There is, for example, a file called dev-calendar.aiml, which responds to questions of the type “What’s the date?” “What’s today?” or “What time is it?”This particular file is interesting because it exemplifies the use of ECMAScript and Rhino to invoke the Java Date class.
Lib Folder—Alice ProgramD’s Java Library The lib folder, shown in Figure 8.2, contains several jar files. Until you add the jabberbeans.jar, you won’t see that one.You may want to customize and build the Aliceserver.jar (for example to incorporate your own version of the Jabber logic), so it may be sized differently.
Figure 8.2 Alice jar files.
The aliceserver.jar contains the basic Alice logic plus the custom Jabber library we will build in this chapter.The Jabber multiplexor (mux) code, which we also build, is bundled into JabberLib.jar. Jabberbeans.jar is the standard Java Jabber capability. The js.jar holds the Rhino ECMAScript interpreter. Other libraries are mysql.jar (database services), servlet.jar (for HTML Alice clients), xerces.jar (XML parser), and org.mortbay.jetty.jar (embedded Web server capabilities). As we suggested earlier, Alice plugs in a number of service capabilities by default and allows you to extend the available services.The configuration file startup.xml informs the Alice server of extensions.
Alice Configuration Files Let’s take a closer look at startup.xml so that we can understand Alice generally and see where the Jabber mux is to be plugged in to. 1: 2: 3: 4:
303
304
Chapter 8
Jabber and Conversational Software Agents
The tag (lines 5 and 79) surround everything of significance in the file.The first bot specified is called Alice, the second (specified after line 61) describes another chatterbot.You can change out the bot you’re talking with on the fly by sending the message /talkto . Each chatterbot can have its own knowledge base; you can therefore have bots knowledgeable in a variety of subject domains if you like. 5: 6: 7: 9: 10: 11: 12:
The tag pair (lines 15 and 43) define the muxes that will link Alice to various transports. Shown and defined are an IRC (Internet relay chat) mux (lines 2227), an AOL Instant Messenger mux, and most importantly, a new mux for Jabber transport, AliceJabber (lines 22-27), which will be defined and created in this chapter (shown in bold).We define a host, userid, password, and resource.To use this mux to the Alice, you should create a client that corresponds to this definition, using a GUI client or one of the scripts you saw in Chapter 1. For example, you can do: python newUser.py localhost alice alice Alice
You can specify whatever parameter values you want in ues must correspond to the client you create.
startup.xml, but
13: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: … 34: 35:
these val-
The Alicebot Server
36: 37: 38: 39: 40: 41: 42: 43:
You can leave the files specified in these tags alone unless you want to get into the details of how Alice recognizes sentence ends, compensates for misspelling, and so on. 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:
The tag pairs specify the sources for hard-coded knowledge files.You can update and save startup.xml, and the server will incorporate the changes in the knowledge base. 54: ../aiml/standard/*.aiml 55: ../aiml/extensions/*.aiml 56: ../aiml/*.aiml 57: 58: 61: 62: 63: 64: 65: 66: 67: 68: 69: ../aiml/standard/*.aiml 70: 71: 72:
That’s about all there is to configuring the Alice Server. Just remember to add the stanza for the AliceJabber mux.
305
306
Chapter 8
Jabber and Conversational Software Agents
ALICE Static Knowledge (AIML) Files This section describes enough AIML for general understanding and to get you started, should you wish to add knowledge to your chatterbot-based applications. AIML enables you to input potential questions and static responses into Alicebots. It also enables you to embed tags to invoke arbitrary external operating system calls (the tag), and to call ECMAScript methods inline (the tag), returning the result as a string, which is then output to the user.This latter capability is especially handy in Java versions of the Alicebot server because you can then invoke arbitrary Java classes[2]. Each stanza of AIML provides property desciptions for constructing instances of an object called an AIML object. Properties may also dictate server logic. (For example, when a range of possible responses is given for a question, the server will know to choose one randomly.) More generally, the server uses AIML tags to transform a reply into a script that can save data, activate other programs, give conditional responses, and recursively call a pattern-matching algorithm to insert the responses from other categories.
The Category Tag A stanza of AIML is delimited by a ... pair; the category defines a basic atom of knowledge in AIML. Each category tag contains an input question, an output answer, and an optional context.The input question may just be a pointer to another category tag.
The Pattern Tag The question asked by the user and transported via Jabber is contained in a ... pair.The AIML pattern language is simple, consisting only of words, spaces, and two wildcard symbols, “_” and “*”.Words are letters and numerals, but no other characters.What’s contained between tags is case agnostic.Words must be separated by a single space, and the wildcard characters function like words.
The Template Tag The bot’s response is contained in a ... pair. As mentioned previously, the response can be either a string or an external call returning a string.The template can also set internal variables within Alice, as we illustrate later in this chapter.
The Topic Tag The tag appears outside the category and collects a group of categories together.The topic may be set inside any template. 2 Alicebot servers are written in a number of different languages—Perl, for example.We’re more concerned with demonstrating integration of Jabber with lots of interesting clients rather than doing an exhaustive Alice reference book, so Java seemed a nice common denominator language. If you do have the time and desire to do an intergration of the Perl-based server and Perl Jabber libraries, the architectural approach illustrated in Figure 8.10, later in this chapter, is still relevant.
ALICE Static Knowledge (AIML) Files
Let’s look at an AIML file. First of all, don’t forget that AIML is XML, so we need the “obligatory” XML identifying information, and for good measure, a version ID for which version of AIML we’re using:
Here’s a simple question-and-answer pair. If you specifically ask “What is COUGAAR?” or some variant, you’ll get the response shown in the template. WHAT IS COUGAAR Cougaar is a large-scale agent architecture project that originated under DARPA, and has been made available to the general public through open source licensing. Cougaar provides developers with a framework to implement large-scale distributed agent applications with minimal consideration for the underlying architecture and infrastructure.
Figure 8.3 shows a Jabber Client asking that very question in a chat window.
Figure 8.3 Conversing with Alice.
307
308
Chapter 8
Jabber and Conversational Software Agents
Note that “Alice” is on the client’s roster, because we enabled that with the AliceJabber mux.The user has chosen “Chat” from Exodus’ context menu, and is seen chatting with an Alicebot. After some “Eliza [3]-style” badinage, all of which is guided by other bits of AIML oriented toward conducting trivial conversation, the user ignores the shallow attempt at small talk and asks the question, “What’s COUGAAR, please?”The bot has some rules about question style, one of which can be stated, if you ever see “please” in a conversation, then say something nice about the user’s politeness. It looks like this: _ PLEASE Your polite style is very nice. polite
Notice the tag. As we said before, a template can also set internal variables within Alice. Alice has a dictionary property called “personality,” one key of which is politeness. In this case, Alice is told to believe that you’re polite.This rule fires before the actual question, so that’s why its response outputs “Your polite style is very nice.” Alice also has symbolic reduction rules for contractions; that’s why even though the user asked “What’s COUGAAR, please?” the stimulus question matched WHAT IS COUGAAR. Given another pattern, and another question and answer, we see that Alice has a few other tricks up her (virtual) sleeve, as shown in Figure 8.4. WHO IS MARK GREAVES Dr. Mark Greaves is the DARPA Program Manager for Ultralog.
The user asks “Who the heck is Mark Greaves?” and Alice has a set of random quips ready for the general pattern WHO * , where “*” is a wild card. This one intercepts and fires because the pattern specified earlier would have matched on only the specific string, “Who is Mark Greaves?” As you can see, when the question was asked again, Alice responded with a remembrance of the previous question, plus the correct response. You can also instruct Alice to make some random choices with the tag, followed by a list of responses contained in
... pairs: WHO IS DANA MOORE
3 Chatterbots like Alice are patterned after Eliza, also called Doctor or Psychiatrist. Eliza was written in the 1980s in Lisp. Eliza was/is a conversational bot that many people believed was real. More than a few individuals spent many hours in seeking personal help from Eliza.
ALICE Static Knowledge (AIML) Files
Dana can be contacted at [email protected] Dana Moore is a Senior Scientist with BBN Technologies in Arlington, VA. Dana Moore is really cool Dana Moore is a terrific guitarist Dana Moore is an excellent volleyball setter Dana Moore is an adjunct professor WHO IS BILL WRIGHT
Bill Wright is a Division Engineer with BBN Technologies in Arlington, VA. Bill Wright is a nerd He’s a terrific musician Bill Wright is an excellent lecturer
Figure 8.4 Identifying a person.
Check out the resulting dialogue in Figure 8.5; we’re certainly glad Alice didn’t call Bill a nerd.
309
310
Chapter 8
Jabber and Conversational Software Agents
Figure 8.5 Inquiring about your humble authors.
Here’s a pattern that describes our corporation and would enable an AliceBot to respond intelligently to general queries about your company.You should, of course, adjust accordingly: WHAT IS BBN For over 50 years, the BBN name has been synonymous with technological innovation. Since implementing and operating the ARPANET, the forerunner of today’s Internet, we have been responsible for a number of networking firsts: the first packet switch, the first router, and the first person-to-person network email. We also designed, built, and operated the Defense Data Network. Today at BBN, some of the same scientists involved in those projects are working with the brilliant young graduates of the country’s top technical schools to pioneer new innovations.
Wild-carding is done with an “*”. If you ask Alice anything followed by “BBN,” it will respond accordingly, as you can see in Figure 8.6.[4] Finally, stimuli don’t have to be in the form of a question [5]: 4 Note that in Figure 8.6, Alice at first claims not to know anything about BBN, then gives the full dump.We believe that this may be an artifact of the way in which we configured the knowledge base and also a program bug. Alice looks in the root folder first for her knowledge. She didn’t find the pattern there, and therefore added the string I do not know what BBN is. to the response buffer. Later, she found the correct response template and added that as well. 5 Pattern stimuli don’t need to be in all uppercase.This just seems to be a custom in the Alice community.
ALICE Static Knowledge (AIML) Files
Figure 8.6 Asking some more questions. PYTHON CONFUSES ME I would expect such a comment from an untrained user! Python can be learned in under an hour by a competent programmer.
AIML currently supports two ways to interface with other languages and systems.The tag executes any program accessible as an operating system shell command and inserts the results in the reply. Similarly, the tag allows arbitrary scripting inside the templates. The optional context portion of the category consists of two variants, called and , shown previously.The tag appears inside the category, and its pattern must match the robot’s last utterance; it’s how Alice remembered that it had made that “Tony Blair” crack earlier. Remembering one last utterance is important if the robot asks a question. Here’s an example of an external system call. Note that it’s embedded within the tag.You can use any appropriate executable command line; obviously you want to choose something that returns a result to the command’s system output. Here we show a template that responds to questions such as “How is localhost doing?”
HOW IS LOCALHOST * The information I have is as follows: “c:\cygwin\bin\ps -eaf”
311
312
Chapter 8
Jabber and Conversational Software Agents
The template responds, “The information I have is as follows:”, then concatenates the system output of the ps command (see Figure 8.7).
Figure 8.7 Inspecting system status.
Note that if you’re inspecting the output directly from the Alice Server, you’ll see the result of every query, every response, and every system call: Waiting. . . Done Waiting. UID PID PPID TTY STIME COMMAND Dmoore 568 1 con 11:42:46 /cygdrive/c/JabberD/jabberd Dmoore 2460 568 con 11:42:47 /cygdrive/c/WINNT/jabadns Dmoore 2468 1 con 11:51:29 /usr/bin/bash Dmoore 2596 1 con 16:36:36 /usr/bin/ps [16:36:36] System process exit value: 0
The Waiting. . . represents the timeout for which the server is willing to wait upon the execution of the embedded command.The additional output reflects what gets sent to the AliceJabber mux and then forwarded to the Jabber client. You can no doubt think of better and more relevant (to your job) system calls to embed, but can you imagine how cool it would be to shove a Jabber IM client in front of your local version of Dilbert’s “pointy haired boss” and typing in something like “What’s the state of Project Wooly Mammoth,” then having an Alicebot respond? If your pointy hair is like ours, the response would either be to ask, “Who’s Alice? I don’t recall an Alice working for me,” or to continue to do his or her impression of an inanimate object. At least you will have fun with it, though. Here’s an example of an ECMAScript call. Notice that the ECMAScript code itself is set off as a blob of CDATA.The response to the stimulus, “What time is it?” first emits “The time is.”Then the embedded Rhino interpreter interprets the script, which first creates a Java Date object: var now = new java.util.Date(). It then parses the instance, getting the hours and minutes, and creates a de facto ECMAScript string, which is the value returned from the method.
ALICE Static Knowledge (AIML) Files
WHAT TIME IS IT The time is = 12) { hour -= 12 ampm = “PM” } else ampm = “AM” hour = (hour == 0) ? 12 : hour // add zero digit to a one digit minute if (minute < 10) minute = “0” + minute // do not parse this number! hour + “:” + minute + “ “ + ampm; ]]>
The template emits, “The time is ,” followed by the output of the script (see Figure 8.8).
Figure 8.8 Does anybody really know what time it is?
313
314
Chapter 8
Jabber and Conversational Software Agents
If you’re looking at the server’s output.You’ll see AliceJabber echoing the message plus the call to the Rhino interpreter: [16:38:59] AliceJabber: Message from [dana]: what time is it? [16:38:59] Log.devinfo: Calling JavaScript interpreter ➥org.alicebot.server.core.interpreter.RhinoInterpreter
Finally, here’s an ECMAScript that shows Alice nicely demonstrating the capability to extend her knowledge from an outside source.The stimulus will be “lookup word ,” followed by a single string (see Listing 8.1). Listing 8.1 ECMAScript to Fetch Outside Data 1: 2:LOOKUP WORD * 3: 4: 5: 6: var word = ‘’; 7: 50: 51: 52:
In this script, the “*” represents the word to be looked up. It gets substituted into the script itself (line 2). Note that you must put AIML tags and any arguments that you want the script to consume outside the CDATA section (line 6).The script is going to create a socket connection to a dictionary site (lines 8-9) and provide word as input to the site.We create a socket on line 22, using the standard java.net.Socket, and multiplex writers and readers on top of the socket (lines 25-27).We wait 5 seconds for a response (_socket.setSoTimeout(5000)). If the site returns some good HTTP results, we keep cycling until we get a “.” by itself, at which time we will have collected the definition and appended it to the StringBuffer _buffer (line 40). Does it work? See for yourself in Figure 8.9.
Figure 8.9 Dictionary bot.
315
316
Chapter 8
Jabber and Conversational Software Agents
Tip Note that Alice needs to see the “;” at the end of each ECMAScript statement. Failing to provide one will generate JavaScript errors.
Isn’t distributed computing fun? We have distributed human clients somewhere on the Net talking to a conversational agent somewhere else on the Net, and the agent using a service on the Net. And the kick is that it’s all seamless from the client’s standpoint.
Fitting the Pieces Together Well, that was a lot of non-Jabber discussion, but Alice is complex in its own right.You can just install the Alicebot server from http://www.alicebot.org and build upon it by adding your own AIML and using ECMAScript. If you’re still with us and want to build your own set of services, we’re ready, too. What we’re going to build is depicted in Figure 8.10. Let’s inventory the necessary downloads first: You should grab the JabberBeans library, jabberbeans.jar, from http://www.jabberstudio.org. If you want to build it from source, just follow the directions on that site. You will need Java 1.4 or above as well.We believe that anything previous may cause problems. Java is available in source or binary form from http://java.sun.com. You should get and install the latest Alicebot server (originally called ProgramD). After installing, run it just to verify that it works.You can get it from https://sourceforge.net/projects/charliebot/.You are going to need to get the complete source because we will be adding a module to it and then recompiling the project. Of course you need to get and install a jabberd server.You’ve already done that, right? If not, check out Chapter 2, “Installing and Configuring Jabber Software.” n
n
n
n
As depicted in the figure, the basic Alice server operates on built-in knowledge supplied from AIML files, whose structure was covered earlier. It also builds in a capability for knowledge extension via the use of ECMAScript. Finally, it accepts listeners to be plugged in. As Figure 8.10 shows, the listener built here will appear to be an ordinary client; thus other clients may add “alice” to their rosters as two clients (bill@localhost and dana@localhost) have done by adding a client called alice@localhost to their respective rosters. An Alicebot listener must extend org.alicebot.server.net.listener. AliceChatListener. An AliceChatListener’s constructor consumes an org.alicebot. server.core.Bot, which is the chatterbot for which this listener is valid. Muxes can be added so long as they extend this relatively simple class.
Fitting the Pieces Together
Internet Content Providers
ECMAScript
ALICE Server Built-In Knowledge
M U X e s
AliceJabber JabberLib Client Emulation
Jabberd
-
Exodus - ... Exodus Tools
X
Help
Exodus - bill@localhost + +
+ +
JabberBook alice caitlin dana jane
-
Exodus - ... Exodus Tools
+ +
X
Help
Exodus - dana@localhost + +
Friends caitlin ? jane JabberBook alice bill
Available
Available
Figure 8.10 Alice-Jabber architecture.
Extending this class permits interaction with an Alice chatterbot, but we haven’t taken care of enabling ordinary Jabber clients to interact with Alice yet. For this, you need to implement the JabberBeans PacketListener interface to receive notification of new messages. The Alice Java code tree resembles this (as of this writing): org/ org/alicebot/ org/alicebot/gui/ org/alicebot/server/ org/alicebot/server/core/ org/alicebot/server/core/interpreter/ org/alicebot/server/core/loader/ org/alicebot/server/core/logging/ org/alicebot/server/core/node/ org/alicebot/server/core/parser/ org/alicebot/server/core/processor/
317
318
Chapter 8
Jabber and Conversational Software Agents
org/alicebot/server/core/processor/loadtime/ org/alicebot/server/core/responder/ org/alicebot/server/core/targeting/ org/alicebot/server/core/targeting/gui/ org/alicebot/server/core/util/ org/alicebot/server/net/ org/alicebot/server/net/listener/ org/alicebot/server/net/servlet/ org/alicebot/server/sql/ org/alicebot/server/sql/pool/
The AliceJabber Mux Code Listing 8.2 provides the source for the AliceJabber class. Listing 8.2 The AliceJabber Class 1:package org.alicebot.server.net.listener; 2: 3:import java.net.InetAddress; 4:import java.util.Hashtable; 5: 6:import org.alicebot.server.core.*; 7:import org.alicebot.server.core.logging.Log; 8:import org.alicebot.server.core.responder.TextResponder; 9:import org.jabber.jabberbeans.*; 10:import org.jabber.jabberbeans.Extension.IQAuthBuilder; 11:import org.jabber.jabberbeans.util.*; 12: 13:public class AliceJabber extends AliceChatListener 14: implements PacketListener { 15: private String host, userid, password, resource; 16: private static final String MSG = “AliceJabber: “; 17: public static final String label = “AliceJabber”; 18: 19: private ConnectionBean cb; 20: 21: public AliceJabber(Bot bot) { 22: super(bot, “AliceJabber”, new String[][] { { “host”, “” }, { 23: “userid”, “” }, { 24: “password”, “” }, { 25: “resource”, “” } 26: }); 27: } 28: 29: public boolean checkParameters() { 30: try {
The AliceJabber Mux Code
Listing 8.2 Continued 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75:
host = (String) parameters.get(“host”); userid = (String) parameters.get(“userid”); password = (String) parameters.get(“password”); resource = (String) parameters.get(“resource”); } catch (Exception e) { logMessage(e.getMessage()); return false; } return true; } public void receivedPacket(PacketEvent pe) { Message msg = (Message) pe.getPacket(); String msgFrom = msg.getFromAddress().toString(); logMessage(“Message from [“ + msgFrom + “]: “ + msg.getBody()); if (msg.getBody() != null) { MessageBuilder mb = new MessageBuilder(); mb.setToAddress(msg.getFromAddress()); mb.setFromAddress(msg.getToAddress()); mb.setSubject(msg.getSubject()); mb.setThread(msg.getThread()); mb.setType(msg.getType()); String botResponse = Multiplexor.getResponse( msg.getBody(), msgFrom, botID, new TextResponder()); mb.setBody(botResponse); try { cb.send(mb.build()); } catch (InstantiationException e) { } } } public void shutdown() { signoff(); } public void run() { try {
319
320
Chapter 8
Jabber and Conversational Software Agents
Listing 8.2 Continued 76: cb = new ConnectionBean(); 77: SyncPacketListener sync = new SyncPacketListener(cb); 78: Packet p; 79: 80: cb.connect(InetAddress.getByName(host)); 81: InfoQueryBuilder iqb = new InfoQueryBuilder(); 82: IQAuthBuilder auth = new IQAuthBuilder(); 83: iqb.setType(“set”); 84: auth.setUsername(userid); 85: auth.setPassword(password); 86: auth.setResource(resource); 87: 88: iqb.addExtension(auth.build()); 89: Packet iq = iqb.build(); 90: p = sync.sendAndWait((ContentPacket) iq, 5000); 91: 92: new MessengerBean(cb).addPacketListener(this); 93: RosterBean rosterBean = new RosterBean(); 94: rosterBean.setIQBean(new IQBean(cb)); 95: rosterBean.refreshRoster(); 96: PresenceBean presenceBean = new PresenceBean(); 97: presenceBean.setConnBean(cb); 98: presenceBean.addPresenceListener(new OKPresenceListener()); 99: PresenceBuilder pb = new PresenceBuilder(); 100: pb.setStatus(“Available”); 101: presenceBean.getConnBean().send(pb.build()); 102: } catch (Exception e) { 103: } 104: } 105: // Utility class to acknowledge presence subscribe/unsubscribe requests 106: private class OKPresenceListener implements PresenceListener { 107: 108: public void subscribe(Presence p) { 109: reply(p, “subscribed”); 110: reply(p, “subscribe”); 111: } 112: public void unsubscribe(Presence p) { 113: reply(p, “unsubscribed”); 114: reply(p, “unsubscribe”); 115: } 116: 117: public void changedPresence( 118: Hashtable t, 119: Presence p, 120: PresenceUserNode n,
The AliceJabber Mux Code
Listing 8.2 Continued 121: String s) { 122: } 123: // Unneeded Jabberbeans PresenceListener methods 124: public void error(Presence p) { 125: } 126: public void subscribed(Presence p) { 127: } 128: public void unsubscribed(Presence p) { 129: } 130: 131: private void reply(Presence p, String message) { 132: try { 133: PresenceBuilder pb = new PresenceBuilder(); 134: pb.setFromAddress(p.getToAddress()); 135: pb.setToAddress(p.getFromAddress()); 136: pb.setType(message); 137: cb.send(pb.build()); 138: } catch (InstantiationException e) { 139: e.printStackTrace(); 140: } 141: } 142: } 143: 144: public void signoff() { 145: logMessage(“Signing off.”); 146: cb.disconnect(); 147: logMessage(“Signed off.”); 148: } 149: private void logMessage(String message) { 150: Log.userinfo(MSG + message, Log.LISTENERS); 151: } 152: public void sendFailed(PacketEvent arg0) { 153: } 154: 155: public void sentPacket(PacketEvent arg0) { 156: } 157: 158:}
It’s important to examine at least a few sections of the code. First, note that you should put your code in the package org.alicebot.server.net.listener. 1:package org.alicebot.server.net.listener;
We include the core Bot class, which gets passed to the constructor. Multiplexor is used in the receivedPacket() method, which handles the dialogue with Alice.
321
322
Chapter 8
Jabber and Conversational Software Agents
Multiplexor uses a class public method, getResponse(), to manage the dialogue (see the following code). In this case, to “multiplex” means “to select one from many inputs.”The Alice Multiplexor handles input from a bot’s clients and keeps track of all their predicate values. In short, this class handles a session from the bot’s point of view. 6:import org.alicebot.server.core.*; 7:import org.alicebot.server.core.logging.Log; 8:import org.alicebot.server.core.responder.TextResponder;
It’s also important to import the JabberBeans classes, so a session can be implemented from Jabber’s point of view. 9:import org.jabber.jabberbeans.*; 10:import org.jabber.jabberbeans.Extension.IQAuthBuilder; 11:import org.jabber.jabberbeans.util.*;
The receivedPacket callback does the heavy lifting for the class. Notice that the JabberBeans PacketListener interface is implemented. It’s going to handle the incoming message from the Jabber client, wait on Alice’s response, and send that back to the Jabber user.The sender’s Jabber ID is extracted from the Jabber message object (line 45,) which will be used to feed the Multiplexor with the sender information it needs.The TextResponder processes and logs input via a given channel (text, HTML, Flash, and so on) on behalf of the Alicebot.This handling is partially asynchronous and partly synchronous. receivedPacket is an asynchronous callback. getResponse waits synchronously on Alice’s answer; the ConnectionBean (line 63) turns the response around to the Jabber client. 43: public void receivedPacket(PacketEvent pe) { 44: Message msg = (Message) pe.getPacket(); 45: String msgFrom = msg.getFromAddress().toString(); 46: logMessage(“Message from [“ + msgFrom + “]: “ + msg.getBody()); 47: if (msg.getBody() != null) { 48: MessageBuilder mb = new MessageBuilder(); 49: mb.setToAddress(msg.getFromAddress()); 50: mb.setFromAddress(msg.getToAddress()); 51: mb.setSubject(msg.getSubject()); 52: mb.setThread(msg.getThread()); 53: mb.setType(msg.getType()); 54: 55: String botResponse = 56: Multiplexor.getResponse( 57: msg.getBody(), 58: msgFrom, 59: botID, 60: new TextResponder()); 61: mb.setBody(botResponse); 62: try {
The AliceJabber Mux Code
63: cb.send(mb.build()); 64: } catch (InstantiationException e) { 65: } 66: } 67: }
Here’s the setup from Jabber’s standpoint: Declare a new ConnectionBean, then register this class as the PacketListener (lines 76, 92).The receivedPacket method already shown does the job of handling messages.The ConnectionBean object connects to the Jabber Server (line 80), authenticates (lines 81–90), gets the roster of users (lines 93–95), and in return, advertises our presence (lines 96–101). Notice the use of a presence listener to respond to presence subscription requests (line 98). 73: public void run() { 74: try { 75: 76: cb = new ConnectionBean(); 77: SyncPacketListener sync = new SyncPacketListener(cb); 78: Packet p; 79: 80: cb.connect(InetAddress.getByName(host)); 81: InfoQueryBuilder iqb = new InfoQueryBuilder(); 82: IQAuthBuilder auth = new IQAuthBuilder(); 83: iqb.setType(“set”); 84: auth.setUsername(userid); 85: auth.setPassword(password); 86: auth.setResource(resource); 87: 88: iqb.addExtension(auth.build()); 89: Packet iq = iqb.build(); 90: p = sync.sendAndWait((ContentPacket) iq, 5000); 91: 92: new MessengerBean(cb).addPacketListener(this); 93: RosterBean rosterBean = new RosterBean(); 94: rosterBean.setIQBean(new IQBean(cb)); 95: rosterBean.refreshRoster(); 96: PresenceBean presenceBean = new PresenceBean(); 97: presenceBean.setConnBean(cb); 98: presenceBean.addPresenceListener(new OKPresenceListener()); 99: PresenceBuilder pb = new PresenceBuilder(); 100: pb.setStatus(“Available”); 101: presenceBean.getConnBean().send(pb.build()); 102: } catch (Exception e) { 103: }
If you keep the default Alicebot configuration, just put the aaa-jabberbook.aiml file in the AIML/standard directory of your Alice installation.The server will find the file there and load it when it starts.
323
324
Chapter 8
Jabber and Conversational Software Agents
To add the AliceJabber listener to your Alice server, you need to edit the class org.alicebot.server.net.listener. AliceChatListenerRegistry in the ProgramD distribution. Find the PROCESSOR_LIST variable and add the AliceJabber classname to the
list like this: private static final String[] PROCESSOR_LIST = { “org.alicebot.server.net.listener.AliceAIM”, “org.alicebot.server.net.listener.AliceICQ”, “org.alicebot.server.net.listener.AliceJabber”, “org.alicebot.server.net.listener.AliceIRC”};
As we mentioned earlier, to add the AliceJabber listener to your Alice server, add these lines to the section of startup.xml:
This tells the Alice server about the new listener and configures the Jabber connection values.You need to change the host, userid, password, and resource values to match your installation, of course. The AIML in the example uses JavaScript, so you need to edit the server.props file to enable it. Search for the main ProgramD configuration section and update this entry: # Allow use of element? (true/false) programd.javascript-allowed=true
Finally, your Alicebot is pretty stupid without the default set of AIML knowledge files.You need to download the standard set from http://www.alicebot.org/ downloads/ and install them in the aiml/standard directory, along with the aaajabberbook.aiml file.
Running ALICE with Jabber When you build your custom AliceJabber Server, then start it (the jabberd should be running first), you’ll see something like this: Starting Alicebot Program D. [13:21:51] Starting Alicebot Program D version 4.1.5 [13:21:51] Using Java VM 1.4.0_01-b03 from Sun Microsystems Inc. [13:21:51] On Windows 2000 version 5.0 (x86) [13:21:51] Predicates with no values defined will return: “undefined”. [13:21:52] Initializing Multiplexor. [13:21:52] Loading Graphmaster. [13:21:52] Starting up with “C:\AliceX\ProgramD\conf\startup.xml”. [13:21:52] Configuring bot “Alice”.
Summary
[13:21:52] Started “AliceJabber” listener for bot “Alice”. [13:21:52] Loaded 287 input substitutions. Connection event: org.jabber.jabberbeans.ConnectionEvent[source= ➥org.jabber.jabberbeans.ConnectionBean@587c94] [13:21:52] Loaded 19 gender substitutions. [13:21:52] Loaded 9 person substitutions. [13:21:52] Loaded 60 person2 substitutions. [13:21:52] Loaded 4 sentence-splitters. [13:21:55] 6000 categories loaded so far. [13:21:57] 12000 categories loaded so far. [13:22:00] 18000 categories loaded so far. [13:22:01] 1 bots thinking with 22013 categories. [13:22:01] Alicebot Program D (c) 1995-2002 A.L.I.C.E. AI Foundation [13:22:01] All Rights Reserved. [13:22:01] This program is free software; you can redistribute it and/or [13:22:01] modify it under the terms of the GNU General Public License [13:22:01] as published by the Free Software Foundation; either version 2 [13:22:01] of the License, or (at your option) any later version. [13:22:01] Alicebot Program D version 4.1.5 Build [00] [13:22:01] 22013 categories loaded in 9.173 seconds. [13:22:01] The AIML Watcher is not active. [13:22:01] HTTP server listening at http://empath:2001 [13:22:02] Interactive shell: type “/exit” to shut down; “/help” for help. [13:22:02] Alice> Hello there Dana and thanks for connecting! [13:22:02] [Alice] Dana>
Notice that in addition to loading the static knowledge base, Alice now starts the Alice Jabber mux and connects to Jabber. There are actually commercial implementations that use an approach similar to the one you’ve just built, including one (www.verbots.com) that uses Alice, plus text-tospeech output and animated on-screen avatars to interact with a user.
Summary The combination of the Alicebot and the Jabber protocols provides a really interesting way for humans and computers to interact.These two open source technologies seem made for each other and their applications are limited only by our imagination.
325
9 Jabber and System Control and Administration
I’m just a technical supervisor who cared too much. Homer Simpson
L
OTS OF TOOLS ARE AVAILABLE for monitoring and controlling computers, but they tend to be very tightly coupled with the systems to be monitored.Tools for Windows systems don’t generally talk to tools for Unix systems, for example.When thinking about how to control a large set of computer systems, several difficulties come immediately to mind: A person can’t sit in front of each one and watch for problems—problems need to be brought to the attention of the operator automatically. Very often, the same operation will need to be carried out on all the computers at once—the operator needs to be able to express, “Do this to these computers.” n
n
n
These computers may not be homogeneous, so any approach to managing them can’t rely on tools that are limited to one operating system or hardware platform.
Those of us who have to manage large networks of computers know that there is no panacea for these problems, but this chapter presents some ideas and examples of how to use Jabber to address them.
Jabber for System Event Monitoring Microsoft Windows and Unix (in its various flavors) have mechanisms for application programs and the operating system to log events that might be of interest to the system
328
Chapter 9
Jabber and System Control and Administration
administrator.These events might be things such as a user login, a service starting or stopping, or a device coming online. Although the concept of event logging is common to both Windows and Unix, they implement them quite differently. On Windows, you use the Event Viewer to examine the event logs. Figure 9.1 shows what the Windows Event Viewer looks like.
Figure 9.1 The Windows Event Viewer.
The Event Viewer contains timestamped entries that say what happened and what component is logging the event. Contrast this with the Unix event logger (called “syslog”), which can be configured to send log events to various places, but they are commonly just written to a file called something like /var/log/messages. Like the Windows event log, this file contains timestamped lines that contain whatever information the component doing the logging thought was important. Here’s an example of a Unix syslog file: # cat Feb 6 Feb 6 Feb 7 Feb 7 Feb 7 Feb 7 Feb 7 Feb 7 Feb 7 Feb 7
/var/log/messages 04:04:26 al su(pam_unix)[15690]: session opened for user news by (uid=0) 04:04:27 al su(pam_unix)[15690]: session closed for user news 04:02:42 al su(pam_unix)[20879]: session opened for user news by (uid=0) 04:02:42 al su(pam_unix)[20879]: session closed for user news 15:06:59 al login(pam_unix)[23156]: session opened for user wwright 15:06:59 al -- wwright[23156]: LOGIN ON pts/0 BY wwright FROM ad 15:07:09 al su(pam_unix)[23203]: authentication failure; logname=wwright 15:07:11 al login(pam_unix)[23156]: session closed for user wwright 15:07:14 al su(pam_unix)[23204]: authentication failure; logname=wwright 15:07:24 al su(pam_unix)[23205]: session opened for user root by wwright
Rather than manually check each of these logs, it would be nice if you could have a service that would watch them for you and send an instant message if an event occurred. Fortunately, Jabber provides a way to unify these disparate logging systems and deliver messages where they need to go. The next example shows a Jabber client that accepts presence subscriptions from other Jabber clients and forwards them any system events that occur. Let’s look first at the client’s view of this service and then go through the code.
Jabber for System Event Monitoring
An instant messaging client can subscribe to the presence of the system logger service just like any other client. Figure 9.2 shows the system logger as a contact in this user’s roster.
Figure 9.2 A roster containing the system logger.
The presence information says that the service is up and running, ready to send log events as they happen.When an event occurs, the service formats the information into a Jabber message and sends it to all clients who have subscribed to the service (that is, all Jabber clients who have exchanged presence subscriptions with the system logger client). These messages appear as incoming messages at the IM client where the user can view them. Figure 9.3 shows an event from a Windows system.
Figure 9.3 A Windows system event.
329
330
Chapter 9
Jabber and System Control and Administration
Because the Jabber protocols are platform independent, we can treat Unix syslog messages exactly the same.The next figure shows a message from a Unix system reporting a system event.
Figure 9.4 A Unix system event.
So, important messages from many different kinds of computers can be collected in the one interface presented by the IM client.This makes it much easier to keep track of several computers at once without missing any important events. Because the ways system events are manifested on Windows and Unix are so different, a flexible language such as Python is ideal. Reading the Unix syslog is easy because it’s just written to a file. Syslog can be configured to send its messages other places (to a TCP socket, for example), but we’ll stick with a simple file for now. Reading the Windows event log requires the use of a fairly obscure part of the Win32 API, so we’ll use the win32all Python extension available from http://www.python.org or as part of the ActiveState ActivePython distribution at http://www.activestate. com.The win32all extensions wrap much of the Win32 C API and expose it as Python functions. The script in Listing 9.1 is the complete implementation of the system log service for Windows and UNIX.We’ll present the code in its entirety here and then go through it piece by piece. Listing 9.1 The System Logger Service 1:import 2:import 3:import 4:import 5:import
traceback re time string threading
Jabber for System Event Monitoring
Listing 9.1 Continued 6:import sys 7:import jabber 8:import socket 9: 10:# Some imports needed for windows only 11:if sys.platform == “win32”: 12: import win32evtlog 13: import win32api, win32con 14: import win32evtlogutil 15: import win32event 16: event_dict={win32con.EVENTLOG_AUDIT_FAILURE:’AUDIT FAILURE’,\ 17: win32con.EVENTLOG_AUDIT_SUCCESS:’AUDIT SUCCESS’,\ 18: win32con.EVENTLOG_INFORMATION_TYPE:’INFORMATION’,\ 19: win32con.EVENTLOG_WARNING_TYPE:’WARNING’,\ 20: win32con.EVENTLOG_ERROR_TYPE:’ERROR’} 21: win32 = True 22:else: 23: win32 = False 24: 25: 26:# convert a windows date string to unix epoch time 27:def date2sec(evt_date): 28: regexp=re.compile(‘(.*)\\s(.*)’) 29: reg_result=regexp.search(evt_date) 30: date=reg_result.group(1) 31: the_time=reg_result.group(2) 32: (mon,day,yr)=map(lambda x: string.atoi(x),string.split(date,’/’)) 33: (hr,min,sec)=map(lambda x: string.atoi(x),string.split(the_time,’:’)) 34: tup=[yr,mon,day,hr,min,sec,0,0,0] 35: sec=time.mktime(tup) 36: return sec 37: 38: 39:class ReaderThread (threading.Thread) : 40: def __init__ (self, log) : 41: threading.Thread.__init__(self) 42: self.log = log 43: if win32: 44: self.ev_handle=win32evtlog.OpenEventLog(None, log) 45: self.wait_handle=win32event.CreateEvent(None, 0, 0, None) 46: self.die_handle=win32event.CreateEvent(None, 0, 0, None) 47: self.flags = win32evtlog.EVENTLOG_BACKWARDS_READ|\ 48: win32evtlog.EVENTLOG_SEQUENTIAL_READ 49: 50: def run (self):
331
332
Chapter 9
Jabber and System Control and Administration
Listing 9.1 Continued 51: try: 52: self.alive = True 53: if win32: 54: self.goWin32() 55: else: 56: self.goUnix() 57: except: 58: print traceback.print_exc(sys.exc_info()) 59: self.die() 60: 61: def die(self): 62: print “die() called” 63: self.alive = False 64: if win32: 65: win32event.SetEvent(self.die_handle) 66: 67: # event reader loop for windows 68: def goWin32(self): 69: # Make sure this is a version of NT 70: if win32api.GetVersion() & 0x80000000: 71: print “This Client requires Windows NT” 72: return 73: 74: computerName = None 75: log = self.log 76: win32evtlog.NotifyChangeEventLog(self.ev_handle, self.wait_handle) 77: numRecords = win32evtlog.GetNumberOfEventLogRecords(self.ev_handle) 78: events = win32evtlog.ReadEventLog(self.ev_handle, self.flags, 0) 79: print “We ate %d records on startup” % numRecords 80: lastTime = time.time() 81: print “We started at time %d” % lastTime 82: while 1: 83: temp_ev_handle=win32evtlog.OpenEventLog(None, log) 84: numRecords = win32evtlog.GetNumberOfEventLogRecords(temp_ev_handle) 85: rc = win32event.WaitForMultipleObjects( 86: (self.die_handle,self.wait_handle), 87: 0, win32event.INFINITE) 88: if rc==win32event.WAIT_OBJECT_0: 89: print “Asked to die...” 90: break; 91: events = win32evtlog.ReadEventLog(temp_ev_handle, self.flags, 0) 92: if not events: 93: break 94: greatestTime = lastTime; 95: for event in events:
Jabber for System Event Monitoring
Listing 9.1 Continued 96: the_time=event.TimeGenerated.Format() 97: seconds=date2sec(the_time) 98: if seconds prompt is ready for a command.The commands to the JXTA shell are modeled on the Unix shell commands, so to get a list of the available commands, type man as shown in Listing 10.2.
Trying Out JXTA
Listing 10.2 A Sample JXTA Shell Command JXTA>man The ‘man’ command is the primary manual system for the JXTA Shell. The usage of man is: JXTA> man For instance typing JXTA> man Shell displays man page about the Shell The following is the list of commands available: cat Concatanate and display a Shell object chpgrp Change the current peer group clear Clear the shell’s screen env Display environment variable exit Exit the Shell exportfile Export to an external file flush flush a jxta advertisement get Get data from a pipe message grep Search for matching patterns groups Discover peer groups help No description available for this ShellApp history No description available for this ShellApp importfile Import an external file instjar Installs jar-files containing additional Shell commands join Join a peer group kdb run the httpd leave Leave a peer group login login man An on-line help command that displays information about a mkadv Make an advertisement mkmsg Make a pipe message mkpgrp Create a new peer group mkpipe Create a pipe more Page through a Shell object peerconfig Peer Configuration peerinfo Get information about peers peers Discover peers publish publish a jxta advertisement put Put data into a pipe message rdv rdvserver No description available for this ShellApp rdvstatus Display information about rendezvous
373
374
Chapter 10
Jabber and JXTA
Listing 10.2 Continued recv Receive a message from a pipe relaystatus Display information about existing relay route Display information about existing route rsh No description available for this ShellApp rshd Remote JXTA Shell Deamon search Discover jxta advertisements send Send a message into a pipe set Set an environment variable setenv Set an environment variable sftp Send a file to another peer share Share an advertisement Shell JXTA Shell command interpreter sql Issue an SQL command (not implemented) sqlshell JXTA SQL Shell command interpreter talk Talk to another peer transports Display information about the message transports in ➥the current group uninstjar Uninstalls jar-files previously installed with ‘instjar’ version No description available for this ShellApp wc Count the number of lines, words, and chars in an object who Display credential information whoami Display information about a peer or peergroup xfer Send a file to another peer JXTA>
The peers command is used to discover and show the list of known peers.When used with the –r argument, it sends a discovery request message; when used without argument, it prints the list of already-discovered peers.When the shell first starts, it knows about only the local peer, but it can find out about other peers by performing a remote discovery request.This is shown in the following shell session: JXTA>peers peer0: name = shell JXTA>peers -r peer discovery message sent JXTA>peers peer0: name = shell peer1: name = otherPeer JXTA>
The first peers command shows only the shell itself.The last peers command shows that another peer (named otherPeer) has been discovered. Of course, this is all happening asynchronously, so more peers may show up in the list at any time.
The JXTA Java Binding API
One simple JXTA application supported by the JXTA shell is a primitive chat function called talk. Using the talk function to send a message requires three steps: 1. Create a pipe advertisement that others can discover and use to send messages to us.The talk –register command creates this advertisement. 2. Start a listener thread to watch for messages on that pipe.The talk –login command starts this thread. 3. Use the talk –u command to send the message. To set up a peer to receive a message, you have to follow the first two steps by typing these commands at the shell prompt: JXTA>talk -register recipient ..................... User : recipient is now registered JXTA>talk -login recipient JXTA>
The peer that is to send the message needs to follow all three steps as follows: JXTA>talk -register sender ..................... User : sender is now registered JXTA>talk -login sender JXTA>talk -u sender recipient .found user’s advertisement attempting to connect talk is connected to user recipient Type your message. To exit, type “.” at begining of line Hello there, recipient. . JXTA>
The first two talk commands establish the local pipe advertisement and start the pipe listener was shown in the recipient’s example.The third talk command is of the following format: talk –u
There is a slight pause while the shell discovers the recipient’s pipe advertisement. After it’s found, the user is prompted for the messages and they are sent on the pipe one message per line.We’ll see how this works in more detail in the example application later in this chapter.
The JXTA Java Binding API As we mentioned, the JXTA protocols are not tied to any particular programming language, but the most robust implementation is in Java. Covering the entire JXTA Java API is well outside the scope of this book, but a quick overview will help with the example to follow.
375
376
Chapter 10
Jabber and JXTA
All JXTA services are offered with respect to a particular peer group, so the PeerGroup class serves as an important handle to access services. PeerGroup has methods such as getDiscoveryService() and getPipeService() to retrieve the discovery and
pipe services for this group.The default peer group, to which all peers belong, is the NetPeerGroup.The PeerGroupFactory class has a static method newNetPeerGroup() that returns a reference to the default group. As you would imagine, the DiscoveryService is used to discover advertisements for things such as peers, groups, and pipes. It maintains a local cache of advertisements, so it has two methods of access. getLocalAdvertisements() returns a set of advertisements matching a query specification. getRemoteAdvertisements() sends a discovery request message to neighboring JXTA peers. The PipeService is responsible for creating InputPipe and OutputPipe objects. Because the default JXTA pipes are unidirectional, we’ll be creating both types to carry out two-way communications. You’d be right to notice that advertisements are an important part of the JXTA protocols.They are created using a static method called AdvertisementFactory. newAdvertisement().This method takes the advertisement type as a parameter and returns an advertisement instance ready for its fields to be filled in. The last JXTA class that we’ll mention here is the Message class. As we mentioned, JXTA messages are made up of message elements, so building a message consists of building a sequence of message elements.You’ll see an example in the next section.
Example: A Jabber-to-JXTA Bridge In this section, we present an example of a simple integration of Jabber with JXTA.This example is a Jabber client that can accept presence subscriptions and forward Jabber messages to JXTA shell peers by using the simple talk protocol you saw earlier. It is also a JXTA peer that can receive talk protocol JXTA messages and forward them to all Jabber clients on its roster. Just for fun, we’ll have it also interpret some Jabber messages as commands to report on the JXTA status: discover—Causes the client to try to discover peer and group advertisements. Any advertisements that are discovered are sent in Jabber messages to everyone on the roster. peers—Causes the client to respond to the sender with a list of the currently known peers. groups—Causes the client to respond to the sender with a list of the currently known peer groups. n
n
n
Before you dive into the code, it’s a good idea to see how it works.The place to start is with a JXTA shell and the JXTA-Jabber client.Then we use a WinJab client to chat with the JXTA-Jabber client about the JXTA peers, groups, and advertisements.We also add the JXTA-Jabber client to the roster (and by extension, add ourselves to its roster).
Example: A Jabber-to-JXTA Bridge
Figure 10.4 shows a chat session in which we ask the JXTA-Jabber client what peers and groups it sees, and then ask it to discover peers and groups.
Figure 10.4 Chatting with the JXTA-Jabber client.
The last chat command, discover, causes the JXTA-Jabber client to send a remote discovery request. As other peers respond to its request, the client sends Jabber messages to all clients on its roster containing the discovered advertisement.They show up as Jabber messages, as shown in Figure 10.5.
Figure 10.5
Advertisements discovered by the JXTA-Jabber client.
CAUTION If a lot of JXTA advertisements are discovered, the JXTA-Jabber client can generate a lot of messages in a short period. The Jabber server may stop servicing messages from the JXTA-Jabber client if its karma value gets too low. See Chapter 2, “Installing and Configuring Jabber Software,” for a discussion of karma and how to configure it.
377
378
Chapter 10
Jabber and JXTA
To send a JXTA talk message from the JXTA shell to the Jabber clients, we use the three variants of the talk command as before: JXTA>talk -register shell ..................... User : shell is now registered JXTA>talk -login shell JXTA>talk -u shell JxtaToJabber .found user’s advertisement attempting to connect talk is connected to user JxtaToJabber Type your message. To exit, type “.” at begining of line Hello from JXTA! . JXTA>
The message “Hello from JXTA!” is sent to all clients on the JXTA-Jabber client’s roster.They are delivered in WinJab as shown in figure 10.6.
Figure 10.6 A message relayed from JXTA to Jabber.
To go the other way, from Jabber to JXTA, we send a Jabber message to the JXTAJabber client and set the subject to the name of the JXTA talker who should get the message.The next figure shows the message in WinJab just before it’s sent. This message is sent to the JXTA talker’s shell and looks like this: JXTA>talk: from JxtaToJabber to shell Message: Hello from Jabber!
NOTE Remember, the JXTA-Jabber client may have to discover the recipient’s pipe advertisement, so there could be a delay before the message gets delivered to the JXTA shell. After the first message to a recipient, other messages will go quickly because the pipe advertisement is cached.
Example: A Jabber-to-JXTA Bridge
Figure 10.7 A message relayed from Jabber to JXTA.
Now that you’ve seen what it does, you can look at how this all works.
Details of the talk Protocol In the preceding discussion of the shell, we alluded to the fact that the communication between the talkers happens on a JXTA pipe.To create and discover these pipes, we need to understand the format of the pipe advertisement used by the talk protocol. Here’s an example of a talk pipe advertisement: 1: 2: 3: 4: 5: urn:jxta:uuid-59616261646162614E5047205032503387C17C5F0C7B479DB15B… 6: 7: 8: JxtaUnicast 9: 10: 11: JxtaTalkUserName.recipient 12: 13:
The important pieces of this advertisement are Line 3.The root element, jxta:PipeAdvertisement, identifies this XML document as a pipe advertisement. Line 5.This is the unique identifier that distinguishes this pipe advertisement from all other pipe advertisements. Line 8.The of this pipe is JxtaUnicast, which means that messages on this pipe flow from one peer to one other peer. Another type of pipe is JxtaPropagate, which enables one peer to send messages to many other peers. n
n
n
379
380
Chapter 10
n
Jabber and JXTA
Line 11. As far as the JXTA protocols are concerned, the element is arbitrary.The shell talk command uses the name to encode its own information: The name of the talker listening on this pipe follows the period. In this case it is “recipient.”
This example is complicated by the asynchronous nature of the JXTA protocols.When a Jabber message arrives, we may not have yet discovered the PipeAdvertisement for the recipient, so in some cases we have to start a discovery request and keep track of the Jabber messages that are waiting to be sent.Then when (actually, if) the recipient’s pipe advertisement is discovered, we can send the queued message.We’ll point out the queue as we go through the example. As is our wont, we’ll present the complete listing here in Listing 10.3 and then examine it in sections. Listing 10.3 The JXTA-Jabber Client 1:package jdh.demos; 2:import java.io.*; 3:import java.net.InetAddress; 4:import java.util.*; 5:import net.jxta.discovery.*; 6:import net.jxta.document.*; 7:import net.jxta.endpoint.InputStreamMessageElement; 8:import net.jxta.endpoint.MessageElement; 9:import net.jxta.id.IDFactory; 10:import net.jxta.peergroup.*; 11:import net.jxta.pipe.*; 12:import net.jxta.protocol.*; 13:import org.jabber.jabberbeans.*; 14:import org.jabber.jabberbeans.Extension.IQAuthBuilder; 15:import org.jabber.jabberbeans.util.*; 16:public class JxtaClient 17: implements DiscoveryListener, PacketListener, PipeMsgListener { 18: 19: 20: 21:
private private private private
ConnectionBean cb; String username, password, resource, server_host; RosterBean rosterBean; JID myJID;
22: private PeerGroup rootGroup; 23: private Vector msgQueue = new Vector(); 24: public JxtaClient(String[] args) { 25: username = args[0];
Example: A Jabber-to-JXTA Bridge
Listing 10.3 Continued 26: password = args[1]; 27: server_host = args[2]; 28: resource = args[3]; 29: try { 30: startJabber(); 31: startJxta(); 32: } catch (Exception ex) { 33: ex.printStackTrace(); 34: } 35: } 36: private void startJxta() throws Exception { 37: rootGroup = PeerGroupFactory.newNetPeerGroup(); 38: 39:
DiscoveryService disco = rootGroup.getDiscoveryService(); disco.addDiscoveryListener(this);
40: 41: 42:
disco.flushAdvertisements(null, DiscoveryService.GROUP); disco.flushAdvertisements(null, DiscoveryService.PEER); disco.flushAdvertisements(null, DiscoveryService.ADV);
43: PipeAdvertisement pipeadv; 44: Enumeration pipeAds = 45: disco.getLocalAdvertisements( 46: DiscoveryService.ADV, 47: “name”, 48: “JxtaTalkUserName.” + username); 49: if (pipeAds.hasMoreElements()) { 50: pipeadv = (PipeAdvertisement) pipeAds.nextElement(); 51: } else { 52: pipeadv = 53: (PipeAdvertisement) AdvertisementFactory.newAdvertisement( 54: PipeAdvertisement.getAdvertisementType()); 55: PeerGroupID pgid = rootGroup.getPeerGroupID(); 56: pipeadv.setPipeID(IDFactory.newPipeID(pgid)); 57: pipeadv.setName(“JxtaTalkUserName.” + username); 58: pipeadv.setType(PipeService.UnicastType); 59: } 60: InputPipe inputPipe = 61: rootGroup.getPipeService().createInputPipe(pipeadv, this); 62: disco.publish(pipeadv, DiscoveryService.ADV); 63: disco.remotePublish(pipeadv, DiscoveryService.ADV); 64: } 65: private void startJabber() throws Exception { 66: cb = new ConnectionBean();
381
382
Chapter 10
Jabber and JXTA
Listing 10.3 Continued 67: 68:
SyncPacketListener sync = new SyncPacketListener(cb); Packet p;
69:
myJID = new JID(username, password, resource);
70: 71: 72: 73: 74: 75: 76: 77:
cb.addPacketListener(new PacketDebug()); cb.connect(InetAddress.getByName(server_host)); InfoQueryBuilder iqb = new InfoQueryBuilder(); IQAuthBuilder auth = new IQAuthBuilder(); iqb.setType(“set”); auth.setUsername(username); auth.setPassword(password); auth.setResource(resource);
78: 79: 80:
iqb.addExtension(auth.build()); Packet iq = iqb.build(); p = sync.sendAndWait((ContentPacket) iq, 5000);
81: new MessengerBean(cb).addPacketListener(this); 82: rosterBean = new RosterBean(); 83: rosterBean.setIQBean(new IQBean(cb)); 84: rosterBean.refreshRoster(); 85: PresenceBean presenceBean = new PresenceBean(); 86: presenceBean.setConnBean(cb); 87: presenceBean.addPresenceListener(new OKPresenceListener()); 88: PresenceBuilder pb = new PresenceBuilder(); 89: pb.setStatus(“Available”); 90: presenceBean.getConnBean().send(pb.build()); 91: } 92: public void receivedPacket(PacketEvent pe) { 93: Message jabberMsg = (Message) pe.getPacket(); 94: try { 95: if ((jabberMsg.getSubject() == null) 96: || jabberMsg.getSubject().equals(“”)) { 97: // This is a ‘discover’ ‘peers’ or ‘groups’ message 98: String command = jabberMsg.getBody().toLowerCase(); 99: if (command.startsWith(“discover”)) { 100: System.out.println(“Discovering....”); 101: rootGroup.getDiscoveryService().getRemoteAdvertisements( 102: null, 103: DiscoveryService.GROUP, 104: null, 105: null, 106: 10); 107: rootGroup.getDiscoveryService().getRemoteAdvertisements( 108: null,
Example: A Jabber-to-JXTA Bridge
Listing 10.3 Continued 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136:
DiscoveryService.PEER, null, null, 10); } else if (command.startsWith(“peers”)) { System.out.println(“listing peers....”); Enumeration ads = rootGroup.getDiscoveryService().getLocalAdvertisements( DiscoveryService.PEER, null, null); StringBuffer response = new StringBuffer(1024); response.append(“Known Peers are: “); while (ads.hasMoreElements()) { PeerAdvertisement peerAd = (PeerAdvertisement) ads.nextElement(); response.append(peerAd.getName()); if (ads.hasMoreElements()) response.append(“, “); } MessageBuilder mb = new MessageBuilder(); mb.setBody(response.toString()); mb.setSubject(jabberMsg.getSubject()); mb.setThread(jabberMsg.getThread()); mb.setFromAddress(jabberMsg.getToAddress()); mb.setType(jabberMsg.getType()); mb.setToAddress(jabberMsg.getFromAddress()); cb.send(mb.build());
137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154:
} else if (command.startsWith(“groups”)) { System.out.println(“listing groups....”); Enumeration ads = rootGroup.getDiscoveryService().getLocalAdvertisements( DiscoveryService.GROUP, null, null); StringBuffer response = new StringBuffer(1024); response.append(“Known Groups are: “); while (ads.hasMoreElements()) { PeerGroupAdvertisement peerGroupAd = (PeerGroupAdvertisement) ads.nextElement(); response.append(peerGroupAd.getName()); if (ads.hasMoreElements()) response.append(“, “); } MessageBuilder mb = new MessageBuilder(); mb.setBody(response.toString());
383
384
Chapter 10
Jabber and JXTA
Listing 10.3 Continued 155: mb.setSubject(jabberMsg.getSubject()); 156: mb.setThread(jabberMsg.getThread()); 157: mb.setFromAddress(jabberMsg.getToAddress()); 158: mb.setType(jabberMsg.getType()); 159: mb.setToAddress(jabberMsg.getFromAddress()); 160: cb.send(mb.build()); 161: } 162: } else { 163: /* Assume it’s a message for a JXTA peer, 164: * Subject contains the dest peer name */ 165: Enumeration ads = 166: rootGroup.getDiscoveryService().getLocalAdvertisements( 167: DiscoveryService.ADV, 168: “Name”, 169: “JxtaTalkUserName.” + jabberMsg.getSubject()); 170: if (ads.hasMoreElements()) { 171: PipeAdvertisement ad = (PipeAdvertisement) ads.nextElement(); 172: sendToPipe(ad, jabberMsg); 173: } else { // can’t do it now. Queue it. 174: synchronized (this) { 175: msgQueue.add(jabberMsg); 176: } 177: rootGroup.getDiscoveryService().getRemoteAdvertisements( 178: null, 179: DiscoveryService.ADV, 180: “Name”, 181: “JxtaTalkUserName.” + jabberMsg.getSubject(), 182: 10); 183: } 184: } 185: } catch (Exception e) {} 186: } 187: public void discoveryEvent(DiscoveryEvent event) { 188: String type = “UNKNOWN”; 189: switch (event.getResponse().getDiscoveryType()) { 190: case DiscoveryService.ADV : 191: type = “ADV”; 192: // We may have discovered a talk pipe advert. 193: // Send any messages that are waiting. 194: checkPendingMessages(event.getSearchResults()); 195: break; 196: case DiscoveryService.GROUP : 197: type = “GROUP”; 198: break; 199: case DiscoveryService.PEER :
Example: A Jabber-to-JXTA Bridge
Listing 10.3 Continued 200: 201: 202: 203: 204: 205: 206: 207:
type = “PEER”; break; } Enumeration results = event.getSearchResults(); while (results.hasMoreElements()) { Advertisement adv = (Advertisement) results.nextElement(); sendToAll(type + “ Advertisement Discovered”, adv.toString()); }
208: } 209: private void sendToAll(String subject, String msgText) { 210: Enumeration roster = rosterBean.entries(); 211: while (roster.hasMoreElements()) { 212: RosterItem item = (RosterItem) roster.nextElement(); 213: MessageBuilder mb = new MessageBuilder(); 214: mb.setToAddress(item.getJID()); 215: mb.setFromAddress(myJID); 216: mb.setSubject(subject); 217: mb.setBody(msgText); 218: try { 219: cb.send(mb.build()); 220: } catch (InstantiationException e) {} 221: } 222: } 223: private synchronized void checkPendingMessages(Enumeration ads) { 224: if (msgQueue.isEmpty()) 225: return; 226: Vector sentMessages = new Vector(); 227: while (ads.hasMoreElements()) { 228: Advertisement ad = (Advertisement) ads.nextElement(); 229: if (ad instanceof PipeAdvertisement) { 230: PipeAdvertisement newPipeAd = (PipeAdvertisement) ad; 231: String name = newPipeAd.getName(); 232: Enumeration waitingMessages = msgQueue.elements(); 233: while (waitingMessages.hasMoreElements()) { 234: Message jabberMsg = (Message) waitingMessages.nextElement(); 235: if (name.equals(“JxtaTalkUserName.” + jabberMsg.getSubject())) { 236: sendToPipe(newPipeAd, jabberMsg); 237: sentMessages.add(jabberMsg); 238: } 239: } 240: } 241: } 242: msgQueue.removeAll(sentMessages); 243: }
385
386
Chapter 10
Jabber and JXTA
Listing 10.3 Continued 244: public void pipeMsgEvent(PipeMsgEvent event) { 245: net.jxta.endpoint.Message msg = event.getMessage(); 246: MessageElement nameElement = 247: msg.getMessageElement(“JxtaTalkSenderName”); 248: MessageElement msgElement = 249: msg.getMessageElement(“JxtaTalkSenderMessage”); 250: String subject = 251: “Jxta Talk Message From “ + new String(nameElement.getBytes(false)); 252: String message = new String(msgElement.getBytes(false)); 253: sendToAll(subject, message); 254: } 255: private void sendToPipe(PipeAdvertisement pipeAd, Message jabberMsg) { 256: try { 257: OutputPipe outputPipe = 258: rootGroup.getPipeService().createOutputPipe(pipeAd, 1000); 259: net.jxta.endpoint.Message jxtaMsg = new net.jxta.endpoint.Message(); 260: MessageElement messageElement = null; 261: 262: 263: 264: 265: 266:
messageElement = new InputStreamMessageElement( “JxtaTalkSenderName”, new MimeMediaType(“text/plain”), new ByteArrayInputStream(username.getBytes()), null);
267: 268: 269: 270: 271: 272: 273:
jxtaMsg.replaceMessageElement(messageElement); messageElement = new InputStreamMessageElement( “JxtaTalkSenderMessage”, new MimeMediaType(“text/plain”), new ByteArrayInputStream(jabberMsg.getBody().getBytes()), null);
274:
jxtaMsg.replaceMessageElement(messageElement);
275: outputPipe.send(jxtaMsg); 276: outputPipe.close(); 277: } catch (IOException e) { 278: e.printStackTrace(); 279: } 280: } 281: // Unneeded JabberBeans PacketListener methods 282: public void sendFailed(PacketEvent pe) {} 283: public void sentPacket(PacketEvent pe) {}
Example: A Jabber-to-JXTA Bridge
Listing 10.3 Continued 284: // Utility class to acknowledge presence subscribe/unsubscribe requests 285: private class OKPresenceListener implements PresenceListener { 286: 287: 288: 289: 290: 291:
public void subscribe(Presence p) { reply(p, “subscribed”); } public void unsubscribe(Presence p) { reply(p, “unsubscribed”); }
292: 293: 294: 295: 296: 297: 298: 299: 300: 301:
// Unneeded JabberBeans PresenceListener methods public void changedPresence( Hashtable t, Presence p, PresenceUserNode n, String s) { } public void error(Presence p) {} public void subscribed(Presence p) {} public void unsubscribed(Presence p) {}
302: private void reply(Presence p, String message) { 303: try { 304: PresenceBuilder pb = new PresenceBuilder(); 305: pb.setFromAddress(p.getToAddress()); 306: pb.setToAddress(p.getFromAddress()); 307: pb.setType(message); 308: cb.send(pb.build()); 309: } catch (InstantiationException e) { 310: e.printStackTrace(); 311: } 312: } 313: } 314: public static void main(String[] args) { 315: new JxtaClient(args); 316: } 317:}
Starting at the end of the listing, line 314 is the main() method, which just instantiates an instance of the JxtaClient class. Its constructor is at lines 24–35.The constructor parses the Jabber username, password, server host, and resource from the command line and uses them to connect to the Jabber server at line 30 (the startJabber() method). At line 31, the call to the startJxta() method initializes the JXTA libraries. Listing 10.4 looks at the startJxta() method in some detail.
387
388
Chapter 10
Jabber and JXTA
Listing 10.4 Initializing the JXTA Libraries 36: private void startJxta() throws Exception { 37: rootGroup = PeerGroupFactory.newNetPeerGroup(); 38: DiscoveryService disco = rootGroup.getDiscoveryService(); 39: disco.addDiscoveryListener(this); 40: disco.flushAdvertisements(null, DiscoveryService.GROUP); 41: disco.flushAdvertisements(null, DiscoveryService.PEER); 42: disco.flushAdvertisements(null, DiscoveryService.ADV); 43: PipeAdvertisement pipeadv; 44: Enumeration pipeAds = 45: disco.getLocalAdvertisements( 46: DiscoveryService.ADV, 47: “name”, 48: “JxtaTalkUserName.” + username); 49: if (pipeAds.hasMoreElements()) { 50: pipeadv = (PipeAdvertisement) pipeAds.nextElement(); 51: } else { 52: pipeadv = 53: (PipeAdvertisement) AdvertisementFactory.newAdvertisement( 54: PipeAdvertisement.getAdvertisementType()); 55: PeerGroupID pgid = rootGroup.getPeerGroupID(); 56: pipeadv.setPipeID(IDFactory.newPipeID(pgid)); 57: pipeadv.setName(“JxtaTalkUserName.” + username); 58: pipeadv.setType(PipeService.UnicastType); 59: } 60: InputPipe inputPipe = 61: rootGroup.getPipeService().createInputPipe(pipeadv, this); 62: disco.publish(pipeadv, DiscoveryService.ADV); 63: disco.remotePublish(pipeadv, DiscoveryService.ADV); 64: }
All JXTA services are defined in the context of a particular peer group, so the first thing we need to do is get a reference to the Net Peer Group—the default peer group to which all peers belong.The call to PeerGroupFactory.newNetPeerGroup() at line 37 returns that reference. Using that peer group, we can get its DiscoveryService (line 38) so we can discover peers, peer groups, and advertisements in the Net Peer Group.The call to addDiscoveryListener() at line 39 registers this object to be called when advertisements are discovered.We implement the JXTA DiscoveryListener interface and its method discoveryEvent() (lines 187–207) to receive notification of discovered advertisements. Of course, we won’t discover anything until we call the DiscoveryService method getRemoteAdvertisements() to send a discovery request to other peers. Lines 40–42 clear the cache of any stored advertisements from any previous times this client was run.
Example: A Jabber-to-JXTA Bridge
The next thing we need to do is create the pipe that other peers can use to send us JXTA talk messages. Lines 43–59 create the advertisement for the talk pipe. At lines 44–50, we look in the local cache to see whether it contains an appropriate pipe advertisement from a previous run by using the DiscoveryService getLocalAdvertisements() method.The parameters to getLocalAdvertisements() determine what advertisement we’re looking for. DiscoveryService.ADV (line 46) tells the discovery service that we’re looking for a generic advertisement—not a peer or peer group. Line 47 (“name”) and line 48 (“JxtaTalkUserName.” + username) tell the discovery service that we’re looking for an advertisement in which the element matches the concatenation of “JxtaTalkUserName.” and our Jabber username.The getLocalAdvertisements() API accepts simple wildcarding (using “*”), but we want an exact match in this case. If the discovery service finds a matching advertisement in the cache, we’ll reuse it (line 50). Otherwise, we create a new PipeAdvertisement (lines 52–58) and initialize its name to the concatenation of “JxtaTalkUserName.” and our Jabber username. Notice line 56, which creates a new unique identifier for the new pipe advertisement. CAUTION The attentive reader may be asking, “If you cleared the advertisement cache (line 42), why bother looking for a cached pipe advertisement (line 45)?” Good question. Some early versions of the Java JXTA platform have a bug that can cause flushAdvertisements() to not work when used like this. To really be sure the cache is flushed, you need to remove the files that JXTA uses to store cached data. They are in the JXTA_HOME directory (usually the directory in which you ran the program) in a directory called cm. This directory can be safely deleted before starting the JXTA program. Flushing the advertisements like this can cause problems for other JXTA clients that may have cached the PipeAdvertisement from a previous run of JxtaClient. They may end up with two different pipe advertisements (remember, the unique ID determines the ad’s identity) with the same value for the element. If so, you’d need to clear their caches, too.
All that’s left to get the JXTA initialization completed is to open the pipe to receive messages using this statement (line 61): rootGroup.getPipeService().createInputPipe(pipeadv, this);
This statement creates a JXTA input pipe that uses pipeadv as its advertisement and registers this as a pipe listener.When a message is received on this pipe, JXTA calls the pipeMsgEvent() method of the PipeMsgListener interface (lines 243–253). Finally, no one will know about this pipe unless we advertise it by using the discovery service methods publish() to publish it locally and remotePublish() to send it to remote peers. Starting the Jabber client is straightforward.The startJabber() method (repeated in Listing 10.5) authenticates to the Jabber server, fetches our roster, and sends our presence.
389
390
Chapter 10
Jabber and JXTA
Listing 10.5 Initializing the Jabber Client 65: private void startJabber() throws Exception { 66: cb = new ConnectionBean(); 67: SyncPacketListener sync = new SyncPacketListener(cb); 68: Packet p; 69: myJID = new JID(username, password, resource); 70: cb.addPacketListener(new PacketDebug()); 71: cb.connect(InetAddress.getByName(server_host)); 72: InfoQueryBuilder iqb = new InfoQueryBuilder(); 73: IQAuthBuilder auth = new IQAuthBuilder(); 74: iqb.setType(“set”); 75: auth.setUsername(username); 76: auth.setPassword(password); 77: auth.setResource(resource); 78: iqb.addExtension(auth.build()); 79: Packet iq = iqb.build(); 80: p = sync.sendAndWait((ContentPacket) iq, 5000); 81: new MessengerBean(cb).addPacketListener(this); 82: rosterBean = new RosterBean(); 83: rosterBean.setIQBean(new IQBean(cb)); 84: rosterBean.refreshRoster(); 85: PresenceBean presenceBean = new PresenceBean(); 86: presenceBean.setConnBean(cb); 87: presenceBean.addPresenceListener(new OKPresenceListener()); 88: PresenceBuilder pb = new PresenceBuilder(); 89: pb.setStatus(“Available”); 90: presenceBean.getConnBean().send(pb.build()); 91: }
Lines 69–79 build the authentication packet to log us in to the Jabber server. In line 80, the sendAndWait() method is a handy way to send a packet and wait for a response.We assume that the Jabber server’s response is a success packet and that it’s okay to proceed. Because we’re interested in only Jabber message packets (and not IQ or presence packets), we add ourselves as a packet listener by using a MessengerBean at line 81.We keep track of the roster by using a RosterBean (lines 82–84) and use a PresenceBean (lines 85–90) to handle our presence status and subscriptions. At this point, Jabber and JXTA are both initialized and we’re just waiting for a message on either of them.When we receive a Jabber message, the receivedPacket() method (lines 92–186) is called. In the real world, this method would probably be refactored because it does several different things. If the message has no subject line, it interprets the body of the message as a command. If the body starts with discover we start
Example: A Jabber-to-JXTA Bridge
remote discovery requests for groups and peers by using getRemoteAdvertisements(). This example starts a discovery request for peer group advertisements. 101: 102: 103: 104: 105: 106:
rootGroup.getDiscoveryService().getRemoteAdvertisements( null, DiscoveryService.GROUP, null, null, 10);
The null value at line 102 means to send this discovery request to all peers within the group.The null values in lines 104 and 105 mean that we’re interested in seeing all groups.These parameters could be used to filter the results in the same way that you saw in the call to getLocalAdvertisements() at line 45. If the body of the Jabber message starts with peers, we respond with a message containing the names of all of the peers that we have discovered (see Listing 10.6). Listing 10.6 Enumerating the Known Peers 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136:
Enumeration ads = rootGroup.getDiscoveryService().getLocalAdvertisements( DiscoveryService.PEER, null, null); StringBuffer response = new StringBuffer(1024); response.append(“Known Peers are: “); while (ads.hasMoreElements()) { PeerAdvertisement peerAd = (PeerAdvertisement) ads.nextElement(); response.append(peerAd.getName()); if (ads.hasMoreElements()) response.append(“, “); } MessageBuilder mb = new MessageBuilder(); mb.setBody(response.toString()); mb.setSubject(jabberMsg.getSubject()); mb.setThread(jabberMsg.getThread()); mb.setFromAddress(jabberMsg.getToAddress()); mb.setType(jabberMsg.getType()); mb.setToAddress(jabberMsg.getFromAddress()); cb.send(mb.build());
Here we use the discovery service getLocalAdvertisements() to search the cache for peer advertisements (line 116).We loop through each advertisement and append its name (line 125) to our response. Finally, we build a Jabber message in response and send it (lines 129–136). Notice that because the thread and type values have been copied from
391
392
Chapter 10
Jabber and JXTA
the source message into the response (lines 132 and 134), if the incoming message is a chat message, the response will appear in the chat window rather than as a separate message. If the body of the incoming Jabber message starts with groups, we respond with a message containing the names of all of the peer groups that we have discovered.This code is almost exactly the same as Listing 10.6, so we won’t repeat it here. The last section of the receivedPacket() method (lines 162–184) interpret the subject line of the incoming Jabber message as the name of a JXTA talk user and the body as the message that we should send to that user. 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184:
} else { /* Assume it’s a message for a JXTA peer, * Subject contains the dest peer name */ Enumeration ads = rootGroup.getDiscoveryService().getLocalAdvertisements( DiscoveryService.ADV, “Name”, “JxtaTalkUserName.” + jabberMsg.getSubject()); if (ads.hasMoreElements()) { PipeAdvertisement ad = (PipeAdvertisement) ads.nextElement(); sendToPipe(ad, jabberMsg); } else { // can’t do it now. Queue it. synchronized (this) { msgQueue.add(jabberMsg); } rootGroup.getDiscoveryService().getRemoteAdvertisements( null, DiscoveryService.ADV, “Name”, “JxtaTalkUserName.” + jabberMsg.getSubject(), 10); } }
First, we look in the local cache for an advertisement that matches the name in the subject of the Jabber message (lines 165–171). If we find the pipe advertisement, we can just send the message immediately (line 172). If we don’t find the pipe advertisement, we need to store the message in a message queue (line 175) and start a discovery request to try to find the pipe advertisement (lines 177–182). If an advertisement is discovered, the JXTA platform calls the discoveryEvent() method (lines 187–207 in Listing 10.7).This could happen either because we received a Jabber message with the body discover or because there are pending messages waiting to be sent.
Example: A Jabber-to-JXTA Bridge
Listing 10.7 A JXTA Advertisement Is Discovered 187: public void discoveryEvent(DiscoveryEvent event) { 188: String type = “UNKNOWN”; 189: switch (event.getResponse().getDiscoveryType()) { 190: case DiscoveryService.ADV : 191: type = “ADV”; 192: // We may have discovered a talk pipe advert. 193: // Send any messages that are waiting. 194: checkPendingMessages(event.getSearchResults()); 195: break; 196: case DiscoveryService.GROUP : 197: type = “GROUP”; 198: break; 199: case DiscoveryService.PEER : 200: type = “PEER”; 201: break; 202: } 203: Enumeration results = event.getSearchResults(); 204: while (results.hasMoreElements()) { 205: Advertisement adv = (Advertisement) results.nextElement(); 206: sendToAll(type + “ Advertisement Discovered”, adv.toString()); 207: } 208: }
If the discovered advertisements are of type DiscoveryService.ADV, they might be a pipe advertisement for a message that we’re waiting to send. Line 194 calls checkPendingMessages() (lines 223–243) with the results of the JXTA discovery to see whether there are any waiting messages. In any event, we send each discovered advertisement to every client on our roster (lines 203–207) by using the sendToAll() method shown in Listing 10.8. Listing 10.8 Sending a Message to Everyone on the Roster 209: private void sendToAll(String subject, String msgText) { 210: Enumeration roster = rosterBean.entries(); 211: while (roster.hasMoreElements()) { 212: RosterItem item = (RosterItem) roster.nextElement(); 213: MessageBuilder mb = new MessageBuilder(); 214: mb.setToAddress(item.getJID()); 215: mb.setFromAddress(myJID); 216: mb.setSubject(subject); 217: mb.setBody(msgText); 218: try { 219: cb.send(mb.build()); 220: } catch (InstantiationException e) {} 221: } 222: }
393
394
Chapter 10
Jabber and JXTA
This method uses the RosterBean to get the list of subscribed clients (line 210) and sends each one a message with the subject and body passed in as parameters. The other thing we have to do when we discover a JXTA advertisement is look through the unsent messages to see whether the advertisement is for a talk pipe for which we have queued messages.The checkPendingMessages() method (repeated in Listing 10.9) does this. Listing 10.9 Sending Queued Message to JXTA 223: private synchronized void checkPendingMessages(Enumeration ads) { 224: if (msgQueue.isEmpty()) 225: return; 226: Vector sentMessages = new Vector(); 227: while (ads.hasMoreElements()) { 228: Advertisement ad = (Advertisement) ads.nextElement(); 229: if (ad instanceof PipeAdvertisement) { 230: PipeAdvertisement newPipeAd = (PipeAdvertisement) ad; 231: String name = newPipeAd.getName(); 232: Enumeration waitingMessages = msgQueue.elements(); 233: while (waitingMessages.hasMoreElements()) { 234: Message jabberMsg = (Message) waitingMessages.nextElement(); 235: if (name.equals(“JxtaTalkUserName.” + jabberMsg.getSubject())) { 236: sendToPipe(newPipeAd, jabberMsg); 237: sentMessages.add(jabberMsg); 238: } 239: } 240: } 241: } 242: msgQueue.removeAll(sentMessages); 243: }
Because we have stored all of the pending messages in the msgQueue vector, we need to loop through all the discovered advertisements and, for each one, loop through all the pending messages.There are certainly more efficient ways to do this, but this is easy to follow.The only tricky thing is we don’t want to delete elements from the vector while we’re iterating through its elements, so we make a temporary vector (sentMessages) to hold the messages that we send and should remove from msgQueue.We delete them all after we’re finished sending messages (line 242). The method in which the JXTA messages are actually sent is called sendToPipe() (lines 255–280 in Listing 10.10).
Example: A Jabber-to-JXTA Bridge
Listing 10.10 Sending a JXTA Message 255: private void sendToPipe(PipeAdvertisement pipeAd, Message jabberMsg) { 256: try { 257: OutputPipe outputPipe = 258: rootGroup.getPipeService().createOutputPipe(pipeAd, 1000); 259: net.jxta.endpoint.Message jxtaMsg = new net.jxta.endpoint.Message(); 260: MessageElement messageElement = null; 261: 262: 263: 264: 265: 266:
messageElement = new InputStreamMessageElement( “JxtaTalkSenderName”, new MimeMediaType(“text/plain”), new ByteArrayInputStream(username.getBytes()), null);
267: 268: 269: 270: 271: 272: 273:
jxtaMsg.replaceMessageElement(messageElement); messageElement = new InputStreamMessageElement( “JxtaTalkSenderMessage”, new MimeMediaType(“text/plain”), new ByteArrayInputStream(jabberMsg.getBody().getBytes()), null);
274:
jxtaMsg.replaceMessageElement(messageElement);
275: outputPipe.send(jxtaMsg); 276: outputPipe.close(); 277: } catch (IOException e) { 278: e.printStackTrace(); 279: } 280: }s
Given a pipe advertisement, we need to create an output pipe before we can send a message. Line 256 uses the pipe service to create the pipe.The second parameter, 1000, tells the pipe service to try for one second (1000 milliseconds) to create the pipe. It’s possible that it could take longer to establish the pipe, so a robust application would handle that possibility by blocking indefinitely (by using –1 as the timeout) or setting up an output pipe listener. NOTE Both the JabberBeans library and the JXTA library include classes named Message, so we have to use the fully-qualified classname for the JXTA Message class in line 259 so the Java compiler knows which one we mean.
395
396
Chapter 10
Jabber and JXTA
A JXTA message is made up of a sequence of message elements and the JXTA shell talk protocol calls for two elements.The first element has the name JxtaTalkSenderName and contains the name of the talker sending the message.The other element has the name “JxtaTalkSenderMessage” and, unsurprisingly, contains the text of the message.The Java JXTA API has many ways to create message elements; lines 261–266 show one of the simplest. It creates a message element with the name “JxtaTalkSenderName”. Every message element has a MIME type that declares what type of data is contained within it. This element is just plain text, so we use the MIME type “text/plain”.This type of message element takes its contents from an input stream, so (line 265) we create a ByteArrayInputStream by using our Jabber username as the data source.The final null at line 266 indicates that this element has no associated digital signature element. Line 267 adds the new message element to the message, replacing any other element with the same name. The other message element is constructed in the same manner, and the whole message is sent to the output pipe at line 275. Now JXTA will deliver the message to the remote shell application. Handling a received JXTA message requires us to pull out the JXTA message elements and send Jabber messages.The pipeMsgEvent method is called when data arrives on the input pipe, as shown in Listing 10.11. Listing 10.11 Receiving a JXTA Message 244: public void pipeMsgEvent(PipeMsgEvent event) { 245: net.jxta.endpoint.Message msg = event.getMessage(); 246: MessageElement nameElement = 247: msg.getMessageElement(“JxtaTalkSenderName”); 248: MessageElement msgElement = 249: msg.getMessageElement(“JxtaTalkSenderMessage”); 250: String subject = 251: “Jxta Talk Message From “ + new String(nameElement.getBytes(false)); 252: String message = new String(msgElement.getBytes(false)); 253: sendToAll(subject, message); 254: }
Lines 246 and 248 show how to extract the talker’s name and message text, respectively. We build a subject line for the Jabber message by using the name of the JXTA sender (line 250) and extract the text of the JXTA talk message for the body of the Jabber message (line 252).The sendToAll() method is the same one you saw in Listing 10.7— it sends the message to every client on the roster. So that’s it.This was one of our longer examples, but it shows many of the details involved in translating between the two technologies. As you can see, it would be possible without too much trouble to turn this example into a Jabber gateway service.
Summary: Jabber and JXTA as Complimentary Technologies
Summary: Jabber and JXTA as Complimentary Technologies We hope that you agree that JXTA and Jabber are technologies that approach the issues of building P2P systems in different and complimentary ways. The configuration of a Jabber network of servers is explicit and expressed in terms of the common Internet infrastructure of DNS and email-style (user@host) names.This type of configuration leverages the work done by countless others in building up the Internet naming structure and TCP/IP into a robust distributed environment. For longlived services and other required infrastructure, this type of explicit configuration makes sense. JXTA’s discovery mechanism adds to the flexibility of a distributed system at the expense of complexity. Discovering a service requires more complicated software than simply referencing a JID from a configuration file, but for services that move or are intermittently available, this complexity is warranted. JXTA’s peer group concept enables the scoping of interactions without regard to the physical topology of the network. Both these approaches are required to build a large-scale but flexible and dynamic peer-to-peer application. Jabber’s simple and scalable infrastructure combined with JXTA’s dynamic configuration is a recipe for powerful P2P applications.
397
11 Jabber Libraries for Popular Languages
Most of life is choices, and the rest is pure dumb luck. Marian Erickson
B
ECAUSE JABBER RELIES ON THE OPEN XML standard for its messaging formats, you have lots of choices with regard to programming languages and Jabber libraries. In fact, this book has shown you examples in several different languages.This chapter looks at libraries for creating Jabber-aware applications, shows some examples, and talks about where they might be appropriate. All these libraries are open-source and available as of this writing.
Jabber-Net—Jabber for the .NET Environment Microsoft’s .NET environment includes such popular languages as Visual Basic and C# (pronounced C-sharp).The Jabber-Net library (or assembly in .NET parlance) works with any of the .NET languages to allow easy access to Jabber facilities. It is available at http://www.jabberstudio.org/projects/jabber-net. Jabber-Net provides a high-level API that isolates the programmer from the details of the Jabber XML protocol, making it well suited for quick development or dragand-drop style programming with Visual Studio.To build a Jabber client, the class jabber.client.JabberClient is the main class you need to use. It has lots of properties, but the most important are listed in Table 11.1.
400
Jabber Libraries for Popular Languages
Table 11.1 JabberClient Properties Property
Description
Agents AutoAgents AutoLogin AutoPresence AutoRoster
The list of agents available on the server. If true, automatically requests the server's list of agents when connecting. If true, automatically logs in when connecting. If true, automatically sends presence information when connecting. If true, automatically retrieves roster information from the server when connecting. The password to use to log in to the server. The TCP/IP port on which to connect to the server. The resource to use when connecting to the server. The name of the Jabber server to use when connecting. The username to use when connecting to the server.
Password Port Resource Server User
The other properties are covered in Jabber-Net’s online documentation. After you set the appropriate properties, calling the Connect() method causes the JabberClient to initiate a connection with the server and exchange stream headers. If any of the Auth* properties are true, other packets may be exchanged also. Here’s a simple example of connecting in C#: JabberClient c = new JabberClient(); c.Server = “my-jabber”; c.User = “user1”; c.Password = “user1”; c.Resource = “Work”; c.AutoAgents = true; c.AutoLogin = true; c.AutoPresence = true; c.AutoRoster = true; c.Connect();
It’s important to note that the Connect() method doesn’t block until the client is connected and the packets implied by the Auto* properties are exchanged.You need to check the IsAuthenticated property before assuming that sending a message will succeed. This library is especially useful for integrating other .NET or COM components into Jabber services. Listing 11.1 gives you an example of using Jabber .NET to drive an Excel spreadsheet from Jabber clients. Listing 11.1 Jabber-Net in C# 1:using System; 2:using System.Threading; 3:using System.Reflection; 4:using jabber.client; 5:using jabber.protocol.client;
Jabber-Net—Jabber for the .NET Environment
Listing 11.1 Continued 6:using Excel; 7: 8:class MainClass { 9: JabberClient jabber; 10: Application excel; 11: _Worksheet sheet; 12: Range sumCell; 13: private int currentRow = 1; 14: private ManualResetEvent die = new ManualResetEvent(false); 15: 16: private void newMessage(object sender, Message msg) { 17: if (msg.Body == “Quit”) { 18: die.Set(); 19: return; 20: } 21: double dValue = 0.0; 22: try { 23: dValue = Double.Parse(msg.Body); 24: } catch (Exception ex) { 25: Console.WriteLine(ex.StackTrace); 26: return; 27: } 28: double value = UpdateExcel(msg.From, dValue); 29: UpdatePresence(value); 30: } 31: bool connected = false; 32: private void newIQ(object sender, IQ iq) { 33: if ((!connected) && jabber.IsAuthenticated) { 34: connected = true; 35: UpdatePresence(0.0); 36: } 37: } 38: private void newPresence(object sender, Presence pres) { 39: if (pres.Type == PresenceType.subscribe) { 40: Presence response = new Presence(jabber.Document); 41: response.Type = PresenceType.subscribed; 42: response.To = pres.From; 43: jabber.Write(response); 44: } 45: } 46: public MainClass() { 47: excel = InitializeExcel(); 48: jabber = InitializeJabber(); 49: die.WaitOne(); 50: jabber.Close(); 51: excel.Quit();
401
402
Jabber Libraries for Popular Languages
Listing 11.1 Continued 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98:}
} private Application InitializeExcel() { Application app = new Application(); app.Visible = true; app.DisplayAlerts = false; Workbooks workbooks = app.Workbooks; _Workbook workbook = workbooks.Add( XlWBATemplate.xlWBATWorksheet); Sheets sheets = workbook.Worksheets; sheet = (_Worksheet)sheets[1]; sumCell = sheet.get_Range(“C1”, Missing.Value); sumCell.Formula = “=SUM(B:B)”; return app; } private double UpdateExcel(String who, double value) { Range range = sheet.get_Range( “A”+currentRow, “B”+currentRow); object [] aRow = new object[2]; aRow[0] = who; aRow[1] = value; range.Value2 = aRow; currentRow++; return (double)sumCell.Value2; } private JabberClient InitializeJabber() { JabberClient c = new JabberClient(); c.Server = “my-jabber”; c.User = “excel”; c.Password = “excel”; c.Resource = “Work”; c.AutoLogin = true; c.Connect(); c.OnMessage += new MessageHandler(newMessage); c.OnIQ += new IQHandler(newIQ); c.OnPresence += new PresenceHandler(newPresence); return c; } private void UpdatePresence(double val) { jabber.Presence(PresenceType.available, “Sum=”+val, “Available”, 0); } public static void Main(string[] args) { new MainClass(); }
Jabber-Net—Jabber for the .NET Environment
This program functions as a Jabber client.When it starts, it opens a new Excel spreadsheet and initializes it to calculate the sum of one column. It accepts messages from other clients and if the body of the message contains just a single number, it adds that number and a record of the sender to the spreadsheet. Excel updates the sum, which the program reflects to clients by way of its presence information. So, the running total is always reflected to everyone who is subscribed to the program’s presence. Figure 11.1 lets you see what it looks like in WinJab.
Figure 11.1
Using presence information for status.
Notice that the mouseover data shows the client is shown in Figure 11.2.
Sum=702.The
spreadsheet as managed by the
Figure 11.2 Maintaining a spreadsheet from Jabber.
Column A is the JID of the sender of each message and Column B is the value that was in the message.This spreadsheet shows three messages, all from user1@my-jabber/Work, with values 123, 234, and 345.The total of Column B is in cell C1.This is the value reflected in the presence.
403
404
Jabber Libraries for Popular Languages
Let’s walk through some of the C# code. If you’re more comfortable with Visual Basic, the equivalent example follows (the line numbers are different, though). Start with the constructor (starting at line 46). It just initializes Excel, initializes Jabber, waits for the quit signal, and closes each one.The InitializeExcel()(lines 54–66) method just sets up the spreadsheet as we described. Looking closer at InitializeJabber(), you can see the important properties of the JabberClient object initialized at lines 80–84.We set the AutoLogin property, so when we connect to the server (line 85) the library logs us in automatically.We don’t set the AutoPresence property because we want to handle presence manually. At lines 86–88, we install three event handlers to handle message packets, IQ packets, and presence packets, respectively. First, look at the newMessage() method at lines 16–30. First it looks to see whether the entire message body is “Quit” and, if it is, we signal the constructor to exit. Otherwise, we try to parse the message body into a double and use it to update the spreadsheet with the call to UpdateExcel() at line 28. UpdateExcel() returns the value in the summation column (C1), which we use to update the presence information in UpdatePresence(). UpdatePresence() itself is pretty simple (lines 91–94). It uses the JabberClient method Presence() to update the presence information.This sends a presence packet to the server, which forwards it to all subscribers. The newIQ() method (lines 32–36) is called whenever the client receives an IQ packet. Because we know that we’ll receive an IQ packet when we successfully log in, we use that occasion to initialize the presence information. Before other clients can see the presence information, we have to allow them to subscribe to it.This is handled by the newPresence() method (lines 38–45), which is called whenever a presence packet is received.We accept all presence subscription requests, so if the packet received is a subscription request, we create a new presence packet of type “subscribed” and send it to the subscriber. As promised, Listing 11.2 shows the equivalent example in Visual Basic. Listing 11.2 Jabber-Net in Visual Basic 1:option strict off 2:imports System 3:imports System.Threading 4:imports System.Reflection 5:imports jabber.client 6:imports jabber.protocol.client 7:imports Excel 8: 9:class MainClass 10: public shared sub Main() 11: dim app as MainClass 12: app = new MainClass() 13: end sub 14:
Jabber-Net—Jabber for the .NET Environment
Listing 11.2 Continued 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60:
private private private private private private
jabber as JabberClient excel as Application sheet as _Worksheet sumCell as Range currentRow = 1 die as new ManualResetEvent(false)
private sub UpdatePresence(val as double) jabber.Presence(PresenceType.available, _ String.Format(“Sum={0}”,val), “Available”, 0) end sub private sub newMessage(sender as object, msg as Message) if (msg.Body.Equals(“Quit”)) then die.Set() return end if dim dValue as double dvalue = 0.0 try dValue = Double.Parse(msg.Body) catch ex as Exception Console.WriteLine(ex.StackTrace) return end try dim value as double value = UpdateExcel(msg.From.ToString(), dValue) UpdatePresence(value) end sub private connected = false private sub newIQ(sender as object, iq as IQ) if ((not connected) and jabber.IsAuthenticated) then connected = true UpdatePresence(0.0) end if end sub private sub newPresence(sender as object, pres as Presence) if (pres.Type.Equals(PresenceType.subscribe)) then dim response as Presence response = new Presence(jabber.Document) response.Type = PresenceType.subscribed response.To = pres.From jabber.Write(response) end if end sub private function InitializeExcel() as Application dim app as new Application()
405
406
Jabber Libraries for Popular Languages
Listing 11.2 Continued 61: app.Visible = true 62: app.DisplayAlerts = false 63: dim workbooks as Workbooks 64: workbooks = app.Workbooks 65: dim workbook as _Workbook 66: workbook = workbooks.Add( XlWBATemplate.xlWBATWorksheet) 67: dim sheets as Sheets 68: sheets = workbook.Worksheets 69: sheet = sheets.Item(1) 70: sumCell = sheet.Range(“C1”, Missing.Value) 71: sumCell.Formula = “=SUM(B:B)” 72: return app 73: end function 74: 75: private function UpdateExcel(who as String, value as double) as double 76: dim range as Range 77: range = sheet.Range(String.Format(“A{0}”,currentRow), _ 78: String.Format(“B{0}”,currentRow)) 79: dim aRow(2) as object 80: aRow(0) = who 81: aRow(1) = value 82: range.Value2 = aRow 83: currentRow = currentRow + 1 84: return sumCell.Value2 85: end function 86: private function InitializeJabber() as JabberClient 87: dim c as new JabberClient() 88: c.Server = “my-jabber” 89: c.User = “excel” 90: c.Password = “excel” 91: c.Resource = “Work” 92: c.AutoLogin = true 93: c.Connect() 94: AddHandler c.OnMessage, addressOf newMessage 95: AddHandler c.OnIQ, addressof newIQ 96: AddHandler c.OnPresence, addressof newPresence 97: return c 98: end function 99: public sub New() 100: excel = InitializeExcel() 101: jabber = InitializeJabber() 102: die.WaitOne() 103: jabber.Close() 104: excel.Quit() 105: end sub 106:end class
Jabber-Net—Jabber for the .NET Environment
Building the Examples Building the C# and Visual Basic examples is a little complicated because you have to extract the type library information for the Excel COM component into a form that the .NET Framework can use.You need to know the path to your excel.exe or Excel9.olb, depending on your version of Microsoft Office, and run these commands: NOTE It’s probably not necessary to understand exactly what these commands do, so don’t sweat the details here. If you’re using Visual Studio .NET, this is done automatically.
First create a key to use to register the Excel component: c:> sn -k Excel.key Microsoft (R) .NET Framework Strong Name Utility Version 1.0.3705.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. Key pair written to Excel.key
Then extract the type library information and register it in the global assembly cache: C:> tlbimp /silent “c:\Program Files\Microsoft Office\Office\Excel9.olb” ➥/keyfile:Excel.key /out:Excel.DLL C:> gacutil -i Excel.DLL Microsoft (R) .NET Global Assembly Cache Utility. Version 1.0.3705.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. Assembly successfully added to the cache
Then we can compile the C# code: C:> csc /debug+ /r:Excel.DLL /r:jabber-net.dll /out:jabber-net-cs.exe Main.cs Microsoft (R) Visual C# .NET Compiler version 7.00.9466 for Microsoft (R) .NET Framework version 1.0.3705 Copyright (C) Microsoft Corporation 2001. All rights reserved.
And the Visual Basic code: C:> vbc /optionstrict- /debug+ /r:Excel.DLL /r:jabber-net.dll /r:System.dll ➥/r:System.Xml.dll /out:jabber-net-vb.exe Main.vb Microsoft (R) Visual Basic .NET Compiler version 7.00.9466 for Microsoft (R) .NET Framework version 1.00.3705 Copyright (C) Microsoft Corporation 1987-2001. All rights reserved. C: >
Now there are two executables: jabber-net-cs and jabber-net-vb.They should behave exactly the same. Run either one and try it out.
407
408
Jabber Libraries for Popular Languages
iksemel—Jabber
for C/C++
A quick pass through www.jabberstudio.org or SourceForge confirms that there are a few options for writing Jabber clients and servers in C and C++. For the Microsoft Windows environment, the JabberCOM library is used for some of the more popular instant messaging clients. Jabber.com makes a package called JECL (Jabber External Component Libraries) for building server components under Unix. JECL is available on the Jabber.com Web site at http://www.jabber.com. But we figure that the best reason to write in C these days is to code close to the metal in a reasonably cross-platform way. For this reason, we’ll use this section to examine a very lightweight Jabber package called iksemel. iksemel is available on the www.jabberstudio.org Web site. Download it and follow the simple instructions to build and install it. If you’re using Microsoft Windows, you may want to get the free Cygwin (http://www.cygwin.com/) package to make the compilation easier. On Linux, the iksemel installation adds a new shared library to the system called libiksemel. So to compile a program that uses the library, you use a command like this: gcc –g –liksemel –o ikdemo ikdemo.c iksemel has the features that you’d expect from a Jabber library, including support for building clients or services, SSL, and generating and handling the standard Jabber packet types. Because this is C, we have to be aware of memory management, but fortunately, iksemel provides a memory pool that makes this a little easier.The great thing about this package is that it’s tiny.The compiled library is only about 30KB—certainly small enough to fit easily on a PDA, for example. To try out iksemel, we’ll implement a very simple little program that just sends a message to a user and exits.You might want to use something like this as a quick way to send a message from a shell script.This command-line program takes several arguments that control what it does.When run without any arguments, it prints its usage: $ ikdemo USAGE: ikdemo
Its six arguments are server—The host name of the Jabber server. username—The username to use when sending the message. password—That user’s password. resource—The resource to use when sending the message. destjid—The JID of the user to receive the message. message—The body of the message to be sent. If this is more than one word, it needs to be enclosed in quotes. n
n
n
n
n
n
iksemel—Jabber for C/C++
When the program runs, it logs in to the Jabber server, sends its message, and immediately logs out. It ignores any messages it might receive.This is an example of the Jabber principle that a client needs to implement only that part of the protocol that it needs to do its job. Here’s an example run: $ ikdemo my-jabber user1 user1 Work user2@my-jabber “Here’s a message from C” user1@my-jabber/Work Sending Message to: user2@my-jabber $
That’s all the client does.The recipient (user2@my-jabber, in this case) receives a headline message, which WinJab displays in a separate tabbed frame as shown in Figure 11.3.
Figure 11.3 Receiving a headline message.
Simple enough; now let’s look at the code. Listing 11.3 shows the entire source.We’ll go through it step by step afterward. Listing 11.3 Sending a Headline Message from C 1:#include 2:#include “iksemel.h” 3: 4:void packetRecv(void *, ikspak *); 5:void usage(char *prog); 6: 7:typedef struct my_data { 8: char * server; 9: char * username; 10: char * password; 11: char * resource; 12: char * dest; 13: char *message; 14: iksparser *parser;
409
410
Jabber Libraries for Popular Languages
Listing 11.3 Continued 15:} my_data; 16: 17:int main(int argc, char *argv[]) 18:{ 19: if (argc != 7) { 20: usage(argv[0]); 21: } 22: my_data *data = (my_data *)malloc(sizeof(my_data)); 23: data->server = argv[1]; 24: data->username = argv[2]; 25: data->password = argv[3]; 26: data->resource = argv[4]; 27: data->dest = argv[5]; 28: data->message = argv[6]; 29: printf(“%s@%s/%s Sending Message to: %s\n”, 30: data->username, data->server, data->resource, data->dest); 31: 32: data->parser = iks_jabber_new(data, packetRecv); 33: 34: if (!iks_connect_tcp(data->parser, data->server, 0)) { 35: printf(“Error connecting to %s\n”,data->server); 36: exit(1); 37: } 38: 39: while(iks_recv(data->parser, 3000)); 40: exit(0); 41:} 42: 43: 44:void usage(char *prog) 45:{ 46: printf(“USAGE: %s “, prog); 47: printf(“ \n”); 48: exit(1); 49:} 50: 51:void packetRecv(void *d, ikspak *pkt) 52:{ 53: my_data *data = (my_data *)d; 54: if (pkt->type == IKS_PAK_STREAM) { 55: iks* x; 56: char *jid = (char *)malloc(1024); 57: sprintf(jid, “%s@%s/%s”, data->username, data->server, data->resource); 58: iksid *id = iks_id_new(NULL, jid); 59: 60: x = iks_make_auth(id, data->password, iks_find_attrib(pkt->x, “id”)); 61: iks_insert_attrib(x, “id”, “auth”);
iksemel—Jabber for C/C++
Listing 11.3 Continued 62: iks_send(data->parser, x); 63: iks_delete(x); 64: free(jid); 65: } 66: if (pkt->type == IKS_PAK_IQ) { 67: if (pkt->subtype == IKS_TYPE_ERROR) { 68: printf(“ERROR Authenticating\n”); 69: exit(1); 70: } 71: iks *x = iks_make_msg(IKS_TYPE_HEADLINE, data->dest, 72: data->message, “thread-id”); 73: iks_insert_cdata(iks_insert(x, “subject”), 74: “A Message From Me”, -1); // -1 for len means use strlen() 75: iks_send(data->parser, x); 76: iks_delete(x); 77: 78: iks_disconnect(data->parser); 79: } 80: 81:} 82:
The first two lines of the file include some definitions that we’ll need, including the standard I/O functions and the iksemel library (iksemel.h). Following two forward declarations of functions (lines 4 and 5), we define a structure that we’ll use to hold the command line data. 7:typedef struct my_data { 8: char * server; 9: char * username; 10: char * password; 11: char * resource; 12: char * dest; 13: char *message; 14: iksparser *parser; 15:} my_data;
The my_data structure also holds a reference to the XML parser that we’ll need to send and receive packets.When we create the parser, we give it this structure and the function to call when a packet is received (named packedRecv): 32: data->parser = iks_jabber_new(data, packetRecv);
All that’s left to do in
main()
is connect to the server:
34: if (!iks_connect_tcp(data->parser, data->server, 0)) {
and process packets as they are received: 39: while(iks_recv(data->parser, 3000));
411
412
Jabber Libraries for Popular Languages
The other action takes place when packets are received and the parser calls our packetRecv() function.The first thing we need to do after connecting is authenticate. Lines 54 through 65 in the packetRecv() function handle this. 54: if (pkt->type == IKS_PAK_STREAM) { 55: iks* x; 56: char *jid = (char *)malloc(1024); 57: sprintf(jid, “%s@%s/%s”, data->username, data->server, data->resource); 58: iksid *id = iks_id_new(NULL, jid); 59: 60: x = iks_make_auth(id, data->password, iks_find_attrib(pkt->x, “id”)); 61: iks_insert_attrib(x, “id”, “auth”); 62: iks_send(data->parser, x); 63: iks_delete(x); 64: free(jid); 65: }
First, we check to make sure the incoming packet is the stream header (line 54). If it is, then we know that the next thing to do is authenticate, so we assemble our JID (lines 55–58) and make an authentication packet (lines 60–61), using the password that we stored in the data structure.Then we send the packet to the server (line 62) and deallocate the memory that we used (lines 63–64). NOTE You may have noticed that we didn’t have to send a stream header. The iksemel library does that for us when it connects.
After we send our authentication IQ packet, the server responds with an IQ packet that tells us whether or not the login was successful. If it was successful, we’ll go ahead and send the message; otherwise we’ll exit with an error status. 66: if (pkt->type == IKS_PAK_IQ) { 67: if (pkt->subtype == IKS_TYPE_ERROR) { 68: printf(“ERROR Authenticating\n”); 69: exit(1); 70: } 71: iks *x = iks_make_msg(IKS_TYPE_HEADLINE, data->dest, 72: data->message, “thread-id”); 73: iks_insert_cdata(iks_insert(x, “subject”), 74: “A Message From Me”, -1); // -1 for len means use strlen() 75: iks_send(data->parser, x); 76: iks_delete(x);
Line 66 checks to make sure this is an IQ packet. If it is, we assume that it’s the response to our authentication packet and check to see whether it is an error response (line 67). Starting at line 71, we construct the message. Line 73 sets the subject (it’s odd that the iks_make_msg function takes the thread-id as an argument, but you have to
JabberBeans—Jabber for Java
set the subject separately) and line 75 sends it on its way. Line 76 cleans up the allocated packet memory. Finally, after sending the message, we disconnect from the server: 78:
iks_disconnect(data->parser);
This causes the
iks_recv()
function to return 0 and the
main()
function exits.
39: while(iks_recv(data->parser, 3000)); 40: exit(0);
The iksemel library is not as fully-featured as those for some of the other languages, but for simple applications where those features are not needed, or those in which multimegabyte libraries are too big, iksemel is a nice alternative.
JabberBeans—Jabber for Java The JabberBeans library is a very complete library for building Jabber clients and server components in Java.We’ve seen many examples in this book that use the JabberBeans library, so we won’t do another one here.We’ve found it to be a very robust and powerful library, with support for everything we’ve needed. Like most of the libraries in this chapter, JabberBeans is available on the http://www.jabberstudio.org Web site. It comes packaged as a single Java archive (JAR) file, which just needs to be added to the Java CLASSPATH of your Jabber program. JabberBeans contains over 200 classes and interfaces for building all types of Jabber packets and interactions. It even includes support for the and packets, which are rarely used by server component developers (see Chapter 5, “Extending the Jabber Server,” for examples). Because it is so comprehensive and flexible, however, JabberBeans can be more difficult to use than other Jabber libraries. For example, simply sending a message may take several lines of code in JabberBeans: MessageBuilder mb = new MessageBuilder(); mb.setToAddress(fromAddress); mb.setFromAddress(toAddress); mb.setSubject(subject); mb.setThread(thread); mb.setType(type); mb.setBody(“Greetings”); try { cb.send(mb.build()); } catch (InstantiationException e) { }
Contrast this with a similar example that uses the jabberlib TCL library: jlib::send_msg $toAddress -type $type -thread $thread \ -subject $subject -body “Greetings”
413
414
Jabber Libraries for Popular Languages
So although there may be nothing you can’t do with JabberBeans, it might take quite a few lines of code to do it. Of course, it’s likely that a project that used JabberBeans would define helper methods to encapsulate frequently-used functionality like this. Still, there’s a reason we chose JabberBeans for many of the examples in this book— it’s a very robust and mature library.
JabberPy—Jabber for Python The other language that we use for examples in this book is Python.The number of extension modules available for Python is staggering and jabber.py is an excellent example. It can be used to build both clients and server components.The library is available from http://jabberpy.sourceforge.net, as are some good examples.To install it into your Python distribution, just unpack the archive file and run # python setup.py install
If you don’t want to install it with your Python site libraries, you can just include the two files xmlstream.py and jabber.py in your $PYTHONPATH. jabber.py can do most anything you’re likely to need in a lightweight, easy-to-use language.
A Cross-Language Example The next three sections cover some Jabber libraries for some other scripting languages: TCL, Perl, and Ruby.We’ll present the same example in all three languages so you can see how they compare.This example of a simple Jabber chat client shows the basics of connecting to the server, sending and receiving messages, and subscribing and exchanging presence information. Each of these three languages support the TK user interface widget library, so the examples are structured pretty much the same and we won’t belabor the GUI setup code. Before diving into the code, let’s look at how the example works. It brings up a small window with a familiar chat window layout.The row of text entry boxes along the bottom enable the user to enter his Jabber ID, password, and the Jabber ID of the person with which to chat. Figure 11.4 shows the initial window. The Connect button on the bottom right causes the client to connect to the server when clicked.While connected, the client can send a message to the other Jabber client by typing it in the box just above the connection information.These messages, as well as messages and presence information from the other client, are displayed in the top, scrollable window. Figure 11.5 shows a chat session in progress.
A Cross-Language Example
Figure 11.4 The initial Chat window.
Figure 11.5 A simple chat client.
This dialog shows that the client (user1@my-jabber) connected and received a roster update that said the other client (user2@my-jabber) was online. Notice that the source of the message is the first token on each line (enclosed in ). User1 sent the message, “Hello, user” and user2 replied “Hello to you, user1,” followed by “I’m going to go to lunch now.” Finally, user2 changed his presence show to “away” and his status “Lunch.” This is reflected in the last line in the scrolled window. After the client is connected, the label on the button changes to Send Presence.When the button is clicked, it updates the client’s presence status from the default “Online” to “Let’s Roll.” User2 can see this in his IM client, as shown in Figure 11.6. Now look at the code that sets up the GUI. Because all three languages use TK, this code is much the same for all three examples.We’ll go through the TCL GUI setup in detail and the others are available with the example code.
415
416
Jabber Libraries for Popular Languages
Figure 11.6 Changed presence.
Listing 11.4 Creating the User Interface 1:proc make_gui {} { 2: global b 3: global jid 4: global pass 5: global otherJid 6: global chatEntry 7: global output 8: 9: set jid “” 10: set pass “” 11: set otherJid “” 12: set chatEntry “” 13: 14: # Create a root window with three partitions in it 15: wm title . “Jabber Chat” 16: 17: frame .button_frame 18: pack .button_frame -side bottom 19: frame .bframe 20: pack .bframe -side bottom 21: frame .tframe 22: pack .tframe -side top 23: 24: # The bottom frame gets a button and three text boxes 25: frame .button_frame.lframe 26: pack .button_frame.lframe -side left 27: label .button_frame.lframe.lab -text “Your JID” 28: pack .button_frame.lframe.lab -side left 29: entry .button_frame.lframe.jid_entry -textvariable jid 30: pack .button_frame.lframe.jid_entry -side right 31:
A Cross-Language Example
Listing 11.4 Continued 32: frame .button_frame.lframe2 33: pack .button_frame.lframe2 -side left 34: label .button_frame.lframe2.lab -text “Password “ 35: pack .button_frame.lframe2.lab -side left 36: entry .button_frame.lframe2.pass_entry -textvariable pass 37: pack .button_frame.lframe2.pass_entry -side right 38: 39: frame .button_frame.lframe3 40: pack .button_frame.lframe3 -side left 41: label .button_frame.lframe3.lab -text “Other JID” 42: pack .button_frame.lframe3.lab -side left 43: entry .button_frame.lframe3.pass_entry -textvariable otherJid 44: pack .button_frame.lframe3.pass_entry -side right 45: 46: button .button_frame.b -text “Connect” -command buttonPress 47: set b .button_frame.b 48: pack .button_frame.b -side right 49: 50: # The top frame gets a multi-line scrolled text window to show messages 51: text .tframe.text -width 80 -height 20 -yscrollcommand “.tframe.scroll set” 52: scrollbar .tframe.scroll -command “.tframe.text yview” 53: pack .tframe.scroll -fill y -side left 54: pack .tframe.text -side top -fill both -anchor n -expand true 55: set output .tframe.text 56: 57: .tframe.text tag configure mine -foreground red 58: .tframe.text tag configure his -foreground blue 59: 60: # The bottom frame gets a single-line text window for message entry 61: entry .bframe.text -width 80 -textvariable chatEntry 62: pack .bframe.text -side bottom -fill both -anchor s 63: 64: bind .bframe.text sendMsg 65:}
The first dozen lines of this example declare and initialize some global variables.The rest of this function is mostly concerned with creating and positioning the various GUI elements, but there are a couple interesting points.The TK library allows variables to be associated with widgets so that when someone changes the widget (by entering text, for example), the associated variable is automatically updated. Likewise, when the value of the variable changes, the widget is automatically updated.This line associates the variable named jid with the appropriate text entry widget: 29: entry .button_frame.lframe.jid_entry -textvariable jid
417
418
Jabber Libraries for Popular Languages
So the jid variable will always reflect the contents of that text entry widget. The large, scrolled window will contain incoming and outgoing messages, as well as presence status messages.To distinguish the messages, we define two TK tags that we can use to make the text red (for outgoing messages) or blue (for incoming messages): 57: .tframe.text tag configure mine -foreground red 58: .tframe.text tag configure his -foreground blue
Presence information will use the default black text color. You can associate functions with TK buttons by using the –command option.This line causes the function buttonPress to be called when the button is pressed: 46: button .button_frame.b -text “Connect” -command buttonPress
The buttonPress function does one of two things, depending on whether the client is connected or not. Here’s the code for buttonPress: proc buttonPress {} { global b global jid global pass global otherJid set curLabel [$b cget -text] # A little cheesy. The first time the button is pushed, it # connects. All subsequent times, it sends the presence. if {[string compare $curLabel “Connect”] == 0} { connectToJabber $jid $pass $otherJid $b configure -text “Send\nPresence” } else { sendPresence } }
It checks to see whether the button’s label is Connect and, if it is, calls the the button label to Send Presence. Otherwise,
connectToJabber function and changes it calls the sendPresence function.
You associate key presses with functions by using the TK bind command.This line causes the sendMsg function to be called when the key is pressed in the message entry box: 64: bind .bframe.text sendMsg
That’s all there is to the GUI. Now the following section looks at the individual Jabber client libraries.
Jabberlib—Jabber for TCL Jabberlib has been around for a while and, although it is incorporated in several projects, it differs from the other libraries explored in this chapter in that it isn’t maintained as a separate library.We’ve found that the Jabberlib included with the Tkabber client works quite well.
A Cross-Language Example
Tkabber is available from http://www.jabberstudio.org. Download it and copy the to the TCL lib directory. It includes a pkgIndex.tcl file that tells the TCL interpreter about Jabberlib. After Jabberlib is installed, you only need to include lines like these in your script to use it:
jabberlib-tclxml
package require jabberlib package require Tclx
The
connectToJabber
function establishes the connection to the Jabber server.
1:proc connectToJabber {myjid passwd chatjid} { 2: global output 3: global auth_result 4: set start_idx [expr [string first “@” $myjid] + 1] 5: set end_idx [expr [string first “/” $myjid] - 1] 6: set server [string range $myjid $start_idx $end_idx] 7: set idx [expr [string first “@” $myjid] - 1] 8: set user [string range $myjid 0 $idx] 9: set sock [socket $server 5222] 10: jlib::connect $sock $server 11: jlib::send_auth $user $passwd aresource recv_auth_result 12: vwait auth_result 13: if {$auth_result == “OK”} { 14: $output insert end “Connected\n” 15: } 16: jlib::send_presence -stat Online 17: jlib::roster_get -command roster_cmd 18:}
Lines 4 through 8 parse the user’s JID into username, server name, and resource. These are used to connect and authenticate to the server. Line 9 creates the TCP socket that we’ll use to exchange messages with the server, and line 10 makes the connection. Line 11 uses the user JID and password to authenticate to the server. Notice the last parameter to jlib::send_auth is a callback function that is called when the authentication response packet is received. proc recv_auth_result {res args} { global auth_result if {$res == “OK”} { set auth_result OK } else { set auth_result ERR } }
419
420
Jabber Libraries for Popular Languages
The auth_result method sets the global auth_result flag based on the server’s response. Line 12 in connectToJabber waits until the auth_result variable is set. Line 16 sends our initial presence information, and line 17 requests our roster.The roster_cmd function is called when the roster is delivered from the server. proc roster_cmd {status} { #status is BEGIN_ROSTER or END_ROSTER # END_ROSTER means the roster is ready to use echo “Roster CMD: $status\n” }
This client doesn’t have any use for the roster, so it is ignored. While connected, the client can send messages to the other client.The sendMsg function is called when the user presses in the message entry field. 1: proc sendMsg {} { 2: global chatEntry 3: global output 4: global jid 5: global otherJid 6: set text $chatEntry 7: set idx [expr [string first “@” $jid] - 1] 8: set uname [string range $jid 0 $idx] 9: $output insert end “ $text\n” mine 10: jlib::send_msg $otherJid -type chat -thread chat_demo_thread \ 11: -subject chat_demo_subject -body $text 12: set chatEntry “” 13:} sendMsg uses several of the global variables to fill out the message fields. First it inserts the outgoing message into the log window by using the tag that represents outgoing messages (line 9), then it sends the message by using the jlib::send_msg command (line 10), and resets the contents of the text entry box (line 12). This version of Jabberlib defines that incoming messages are received at a function called client:message. proc client:message {from id type subject body err thread priority x} { global output set idx [expr [string first “@” $from] - 1] set uname [string range $from 0 $idx] $output insert end “ $body\n” his }
This function accepts a lot of arguments, but we’re interested in only a couple.The method parses the “from” address to get the username to display at the beginning of the line. Lastly, it inserts the body of the message at the end of the output window by using the TK tag for incoming messages.
A Cross-Language Example
Updating our presence is simple.The
sendPresence
function does that:
1:proc sendPresence {} { 2: jlib::send_presence -show chat -stat {Let’s Roll} 3:}
Responding to presence subscriptions is a little more complicated.We need to reply to “subscribe” and “unsubscribe” presence packets and request our own subscriptions as well.The version of Jabberlib that we’re using calls this method client:presence: 4:proc client:presence {from type x args} { 5: switch — $type { 6: subscribe { 7: jlib::send_presence -to $from -type subscribed 8: jlib::send_presence -to $from -type subscribe 9: } 10: unsubscribe { 11: jlib::send_presence -to $from -type unsubscribed 12: jlib::send_presence -to $from -type unsubscribe 13: } 14: default { 15: global output 16: set show “” 17: set status “” 18: foreach {attr val} $args { 19: if {[string match -show $attr]} {set show $val} 20: if {[string match -status $attr]} {set status $val} 21: } 22: if {[string length $show] == 0} {set show “normal”} 23: if {[string length $status] == 0} {set status “Online”} 24: $output insert end “ $from is $show/$status\n” 25: } 26: } 27:}
Lines 6 through 13 handle subscribe and unsubscribe requests. All other presence packets get displayed in the output window prefixed by (line 24). Notice that the arguments come in the form of a hash ($args) and that show and status may be unspecified. If either is not specified, we set their default values (lines 22–23). That’s it. Although the TCL Jabberlib may not be as well packaged as some others, it has lots of good functionality for building clients in TCL.
Net::Jabber—Jabber for Perl You can build server components as well as clients with the Net::Jabber library for Perl. It is available from http://www.jabberstudio.org as well as the CPAN perl
421
422
Jabber Libraries for Popular Languages
archive and depends on a couple other Perl packages: the XML::Stream package implements Jabber’s XML stream protocol, and the Digest::SHA1 package implements the cryptographic hash function required for authentication. XML::Stream is also on the JabberStudio site, and Digest::SHA1 is included with most Perl distributions. Unpack the downloaded archive and run these commands to install Net::Jabber: % perl Makefile.PL % make % make install
You may have to be root to do the make install, depending on your Perl installation. After the libraries are installed, you can build a client by including this line in your Perl source code: use Net::Jabber ‘Client’;
The Perl
connectToJabber
function is straightforward.
1:sub connectToJabber() { 2: my $uname; 3: my $server; 4: my $resource; 5: ($uname, $server, $resource) = ($jid =~/([^@]*)@([^\/]*)\/(.*)/); 6: $connection = new Net::Jabber::Client(); 7: $connection->SetCallBacks(message=>\&messageCB, 8: presence=>\&presenceCB); 9: my $status = $connection->Connect(hostname=>$server); 10: 11: my @result = $connection->AuthSend(username=>$uname, 12: password=>$pass, 13: resource=>$resource); 14: if ($result[0] ne “ok”) 15: { 16: print “ERROR: Authorization failed: $result[0] - $result[1]\n”; 17: exit(0); 18: } 19: $connection->RosterGet(); 20: $connection->PresenceSend(); 21: 22: # Look for jabber messages every 1000 millisecs 23: my $id = Tk::After->new($text, 1000, ‘repeat’, \&pollJabber); 24:}
Line 5 uses Perl’s powerful regular expression capability to parse the user’s JID into username, server name, and resource. Line 6 makes a new Jabber client connection
A Cross-Language Example
object, and line 7 tells it which methods should be called when message and presence packets arrive: messageCB will be called for message packets and presenceCB will be called for presence packets. Line 9 makes the connection to the server, and line 11 authenticates. If the authentication result is not “ok” (line 14), we simply give up and exit. Otherwise, we proceed to get our roster (line 19) and send our initial presence information (line 20).We need to service both the Jabber connection and the GUI, so we can’t just wait for Jabber messages. Line 23 sets up a timer that will look for Jabber messages once per second by calling the pollJabber function.This should be often enough that any delay is not noticeable and still allow us time to service button clicks and the like. sub pollJabber { my $res = 1; while ($res) {$res = $connection->Process(0);} } pollJabber processes all Jabber packets until no more are available (that is, until $connection->Process returns zero). The sendMsg function is called when the user presses in the message entry
field. sub sendMsg { $connection->MessageSend(to=>$otherJid, subject=>”chat_demo_subject”, thread=>”chat_demo_thread”,type=>”chat”, body=>$chatEntry); my $uname; my $server; my $resource; ($uname, $server, $resource) = ($jid =~/([^@]*)@([^\/]*)\/(.*)/); $text->insert(“end”, “ $chatEntry\n”, ‘mine’); $chatEntry = ‘’; }
The hash arguments to $connection->MessageSend configure the destination address, subject, thread, type, and body of the message.The next five lines parse the username from the JID and format it and the message text into a line in the output window, using the TK tag for outgoing messages.The last line resets the message entry field to be empty. Incoming messages are delivered to the messageCB function. sub messageCB { my $sid = shift; my $msg = shift; my $src = $msg->GetFrom(“jid”)->GetUserID(); my $msgtxt = $msg->GetBody(); $text->insert(“end”, “ $msgtxt\n”, ‘his’); }
423
424
Jabber Libraries for Popular Languages
This function uses the GetUserID() method of the JID object to extract the username of the sending client.The last line of the function inserts the body of the message at the end of the output text box by using the TK tag for incoming messages. The sendPresence function adds the source JID and passes its other arguments on to the $connection->PresenceSend method. 1:sub sendPresence { 2: my %args; 3: while($#_ >= 0) { $args{ lc pop(@_) } = pop(@_); } 4: $args{from} = $jid; 5: $connection->PresenceSend(%args); 6:}
The
presenceCB
function is called when a presence packet is received.
7:sub presenceCB { 8: my $sid = shift; 9: my $presence = shift; 10: my $from = $presence->GetFrom(); 11: my $type = $presence->GetType(); 12: switch ($type) { 13: case “subscribe” { 14: sendPresence(to => $from, type => “subscribed”); 15: sendPresence(to => $from, type => “subscribe”);} 16: case “unsubscribe” { 17: sendPresence(to => $from, type => “unsubscribed”); 18: sendPresence(to => $from, type => “unsubscribe”);} 19: } 20: my $show = $presence->GetShow(); 21: my $status = $presence->GetStatus(); 22: if ($show or $status) { 23: $show = “normal” unless $show; 24: $text->insert(“end”, “ $from is now $show/$status\n”); 25: } 26:}
If the presence packet is a subscription request, it is accepted and a reciprocal subscription is requested (lines 13–19). Otherwise, the show and status elements of the presence packet are extracted and appended to the output window text (lines 20–24). As you can see, the Net::Jabber package is very powerful and complete. Many Perl aficionados have put it to good use for building Jabber clients and server components.
Jabber4R—Jabber for Ruby Ruby is a relative newcomer to the list of popular scripting languages. It combines many of the useful features of Python and Perl and can be used to build Jabber clients with the addition of the jabber4r library.
A Cross-Language Example
jabber4r is available at http://www.jabberstudio.org and installs easily into your Ruby distribution. Unpack the jabber4r archive and run: % ruby install.rb
Although jabber4r doesn’t claim to be as complete as other Jabber libraries, it still has the capabilities we need to implement our little chat client.To make the library available in your Ruby application, just add this line to the top of your script: require ‘jabber4r/jabber4r’
First, let’s look at the connectToJabber method, which makes the connection to the Jabber server and authenticates our user. 1:def connectToJabber(myjid, passwd, chatjid) 2: begin 3: $session = Jabber::Session.bind_digest(myjid, passwd) 4: $session.announce_initial_presence 5: $dest = chatjid 6: $myjid = myjid 7: $session.add_roster_listener {|event, roster| 8: rosterCB(event, roster) 9: } 10: $session.add_message_listener {|msg| 11: messageCB(msg.body) 12: } 13: $session.set_subscription_handler {|subscription| 14: subscriptionCB(subscription) 15: } 16: rescue 17: print “Exception: “+$!.backtrace.join(“\n”) 18: end 19:end
jabber4r tries to hide some of the drudgery of initiating a Jabber session by encapsulating the connecting and authentication into the bind_digest method in line 3. Unlike in other scripting languages, it parses the user’s JID (myjid in this case) to find the Jabber server’s hostname. Also the announce_initial_presence method (line 4) handles the routing initialization of the presence mechanism. The next three statements add listeners for roster, message, and subscription events (lines 7, 10, and 13). jabber4r separates packets and sends subscribe, unsubscribe, subscribed, and unsubscribed packets to the subscription handler in the form of Subscription objects, and others to the roster listener in the form of Roster objects. Because the TK –textvariable option is not supported by the Ruby/TK binding (as of this writing), the sendMsg method extracts the message body text directly from the text entry widget (x.widget line 2) and appends it to the output text widget (line 4), using the TK tag for incoming messages. 1:def sendMsg(x) 2: text = x.widget.value().strip
425
426
Jabber Libraries for Popular Languages
3: $input.delete(“0”, “end”) 4: $output.insert(“end”, “ #{text}\n”, “mine”) 5: msg = $session.new_chat_message($dest) 6: msg.set_body(text) 7: msg.set_thread(“chat_demo_thread”) 8: msg.set_subject(“chat_demo_subject”) 9: msg.send(false) 10:end
Lines 5–8 construct a new chat message, and line 9 sends it on its way.The (false) parameter to msg.send tells jabber4r not to wait for a reply. If we passed the parameter true, the send method would return the response message. When messages are received, the messageCB method is called. def messageCB(text) $output.insert(“end”, “ #{text}\n”, “his”) end
It simply parses the username from the JID of our correspondent and appends the message body by using the TK tag for incoming messages. When the Send Presence button is clicked, the sendPresence method sends the updated presence information. def sendPresence presence = Jabber::Protocol::Presence.new( Jabber.gen_random_id, “chat”, “Let’s Roll”) $session.connection.send(presence) end
It simply creates a new
Presence
object and sends it by using the global
$session.connection.
As we mentioned, jabber4r divides presence handling into two parts: presence subscription handling and presence status updates.The subscriptionCB method handles clients’ requests to subscribe to our presence. 1:def subscriptionCB(subscription) 2: case subscription.type 3: when :subscribe 4: subscription.accept 5: $session.subscribe(subscription.from) 6: when :unsubscribe 7: subscription.accept 8: p = Jabber::Protocol::Presence.new(Jabber.gen_random_id) 9: p.type = “unsubscribe” 10: p.to = subscription.from 11: $session.connection.send(p) 12: end 13:end
Summary
The first section of this case statement handles subscription requests (that is, the type of the presence packet equals subscribe). Line 4 uses the jabber4r convenience method Subscription.accept to respond with a packet that accepts the subscription. Line 5 requests a subscription to the other’s presences by using the convenience method subscribe on the $session object. The second section of subscriptionCB handles unsubscribe requests. Again we use the Subscription.accept method to respond positively (line 7), but jabbber4r has no $session.unsubscribe convenience method, so we just build a presence packet manually (lines 8–10) and send it ourselves (line 11). The other aspect of handling presence packets is dealing with updates in others’ presence status.This information is routed to the rosterCB method. 1:def rosterCB(event,roster) 2: action = case event 3: when Jabber::Roster::ITEM_ADDED 4: “item added” 5: when Jabber::Roster::ITEM_DELETED 6: “item deleted” 7: when Jabber::Roster::RESOURCE_ADDED 8: “resource added” 9: when Jabber::Roster::RESOURCE_UPDATED 10: “resource updated” 11: when Jabber::Roster::RESOURCE_DELETED 12: “resource deleted” 13: end 14: text = “” 15: text
XML Namespaces It’s likely that with people all over to world building XML documents, there will be conflicts over element and attribute names in which a particular name means one thing in one context and something else in another. Or similarly, a document may contain a mixture of content that is for use by different applications. XML namespaces were designed to help avoid these problems. A namespace is identified by a Uniform Resource Identifier (URI) and is a collection of names that are used for elements and attributes in a particular context. Although in general XML namespace URIs refer to Web addresses (such as http://www.w3.org/ TR/REC-html40, for example), Jabber’s XML namespaces are simply colon-separated identifiers.They are used to take XML elements as specific types of messages in the Jabber protocol.The special attribute name xmlns is used to declare an XML namespace. Here’s an example of a message using the jabber:iq:register namespace that requests the registration of a new Jabber user: user1 password Work
[email protected]
The xmlns attribute in the tag tells the server that this query should be interpreted as a registration message.There can be more than one xmlns attribute in an element, but that occurs rarely in the Jabber protocol.
437
438
Appendix B
XML Basics
XML Streams XML is a format for documents, but Jabber is a streaming protocol.That is, messages are exchanged in both directions between client and server over time. XML streams is the approach that the Jabber protocol uses to exchange well-formed XML interactively. The entire interaction between the client and the server is a single XML document exchanged piecemeal over time. Jabber messages are elements within that document. That is, from the time the client connects to the server to the time it disconnects, the client sends one XML document. Likewise, from the time the client connects until it disconnects, the server sends the client one XML document. Elements of these two documents are exchanged back and forth as Jabber messages. An XML document always begins with the XML processing instruction and the root element tag.When a Jabber client connects to a server, it sends this data to start the conversation:
The two XML namespaces are significant. xmlns=”jabber:client” identifies this connection as a Jabber client. n
n
xmlns:stream=”http://etherx.jabber.org/streams”
identifies this connection
as a Jabber message stream. So the root element is always named stream:stream in a Jabber protocol stream.This opening tag is called the stream header.The server responds with the start of its own document:
The server includes the same namespaces as the client. NOTE The namespace jabber:client is used for client-to-server connections. Other namespaces are used for server-to-server and server-to-component streams.
After the stream headers are exchanged, either party can send message elements to the other. Either the client or the server can end the session by closing the root element with a closing tag.This tells the other party that the session is over.
C Resources
T
HE JABBER COMMUNITY IS VIBRANT AND expanding, and there are a number of resources you can leverage to get code libraries, answers to protocol or implementation questions, or contribute your own code to the open source efforts. Because of the dynamic nature of any open source program, some of the resources we have listed in this appendix will have changed by the time you read this book, some will have disappeared, and a whole new group of sites will have sprung up.We have listed resources that are correct at the time of publication, but of course your mileage may vary.
Jabber Servers Several Jabber servers are available under various license terms. All the examples in this book were written and tested with the open source reference implementation server, version 1.4.2, from http://jabberd.jabberstudio.org. Jabberstudio also hosts some other open source Jabber servers written for various special purposes. Commercial servers are available from a few companies, including Jabber.com (http://www.jabber.com), Rhombus (http://www.rhombusim.com), Antepo (http:// www.antepo.com), i3Connect (http://www.i3connect.com), and Tipic (http://www. tipic.com).
Jabber Protocol Reference documentation on the Jabber protocol itself is available at
http://www.
jabber.org/protocol.
Jabber Clients The examples in this book use the open source WinJab client and its follow-on, Exodus. Winjab is available at http://sourceforge.net/projects/winjab. Although its authors describe it as “deprecated” in favor of Exodus, at this writing WinJab is a more complete
440
Resources
and reliable client. Exodus is available at http://exodus.jabberstudio.org. A list of open and proprietary clients is at http://www.jabber.org/user/clientlist.php.
Jabber Libraries As this book was being written, http://www.jabber.org/developer/librarylist.php was a repository for code libraries, ranging from C++ clients and servers, to WinCE clients, to Macromedia Flash clients.
Macromedia Flash The Flash Messaging Libraries at http://fml.jabberstudio.org/ are a bundling of Flash MovieClips and ActionScript APIs that enable you to create scalable vector graphic clients based on Flash.The Macromedia developer environments (for example Macromedia Flash Studio) use proprietary technology, but the really interesting thing here is that because there are Flash players for just about every platform under the sun, and especially for Windows CE devices, you may be able to write a single client implementation that will run well everywhere. Also, because ActionScript is just another name for ECMAScript (formerly JavaScript), this is the only implementation in JavaScript that we know of.You might (potentially) decouple the Flash UI and get a nice Rhino/ECMAScript handler library.
JabberBeans JabberBeans, at www.jabberstudio.org/projects/jabberbeans/project/view.php, is a full-featured Java-based library that we have used extensively in examples for this book. If you are a Java adherent (as are we), you will find JabberBeans to be pretty robust and full featured.That being said, it is fairly low level and mechanistic—you have to do things in a pretty procedural way: connect, then advertise your presence, then get your roster, then handle messages and queries through event callbacks. JabberBeans comes with useful and instructive examples and is available open source under an LGPL license.
Alicebots In Chapter 8, “Jabber and Conversational Software Agents,” we showed an approach to creating intelligent responders as Jabber clients.We used the software package available from http://www.alicebot.org/.The Alice software suite is rapidly evolving, but interesting to explore.
JabberPy The jabber.py library, available at http://jabberpy.sourceforge.net, is a relatively complete Python library for Jabber. jabber.py deals with the XML parsing and socket code. It also offers concrete classes for creating and managing clients and all three
Miscellany
message types, and for handling rosters. As we’ve shown in examples in this book, jabber.py works well with GUIs, provided you use the Python threading Queue library so that there’s no conflict between the jabber.py callback handlers and the GUI event loop (in wzPython or PythonCard, for example). jabber.py implements much (but not all) of the Jabber protocol—certainly enough to make reasonable applications. jabber.py requires at least Python 2.0 and the XML expat parser module (included in the standard Python distrubution). It has been updated to support new-style Python classes, and the applications shown in the text were written with either Python 2.2 or Python 2.3.The original development work was done on Linux, so certain example code uses Linux-only capabilities (such as the select socket call). Even so, the examples shown in the book were tested on Win32. jabber.py is available open source under an LGPL license.
Miscellany Some additional fascinating efforts were current as of publication. Several creative clients (for example, Lluna, which uses animated avatars, and BuddySpace, which has an interesting side application for mapping users and associating their IM presence with a physical presence) are available at www.jabber.org. IBM has a Jabber client written in their Sash development environment that uses HTML, XML, and JavaScript to write programs that look like Windows programs (http://sash.alphaworks.ibm.com/download/sashjab/). There’s actually an XML editor called the XML Cooktop (http://www.xmlcooktop. com/) that has a built-in Jabber client to do collaboration. And let’s not forget EMACS; there seems to be a plugin for just about every purpose in EMACS, and Jabber is no exception. Check out http://savannah.nongnu.org/projects/smyrno/. In Chapter 7, “Jabber and Web Services,” we mentioned Jabber-RPC, which is an effort to port XML-RPC requests over Jabber (see www.pipetree.com/jabber/jrpc.html).
441
Index
Symbols (angle brackets), 436 tag, 436-437 3-tier architecture, 274-275 302-510 error codes, 81-82 3270 glass terminals, 274
A ActiveState ActivePython, 330 addDiscoveryListener() method, 388 addressing, 23-24 administration Apache monitoring client, 350-363 changedPresence() method, 362 checkData() method, 361 code listing, 352, 355-359 getStatusData() method, 360 httpd.conf configuration, 350-351 main processing loop, 359 receivedData() method, 362-363 sample messages, 352 sendData() method, 361 startJabber() method, 359-360 Web server status page, 351-352 distributed control, 341-342 group messaging, 343 one-on-one messaging, 342-343 system control script, 344-350
inventory management service, 198-200 import statements, 208 init() method, 213 InvComponent.py, 200-207 inventoryClient.py, 207 messages, 208-210 iqCB() method, 209 isAvailable() method, 213 jabber.xml configuration, 211 jabberd connection, 212-213 messageCB() method, 211 pickled client list, 211-212 process() method, 214 registration, 214, 221 rssAccount.py, 224 rssClient.py, 223 rssComponent.py, 216-221 setOnline() method, 213 setShow() method, 213 setStatus() method, 213 system event monitoring, 327 system logger service, 329-337 Unix syslog file, 328 Windows Event Viewer, 328 version management packets, 338 tag, 338 tag, 338 requesting version information, 338-339
444
administration
responding to version requests, 339-341 tag, 338 administrative users, 58-59 advertisements discovering, 392-393 peer advertisements, 366-368 talk pipe advertisements, 379-380 agents browsable agents, 142-147 querying for, 134-136 Agents property (JabberClient class), 400 AIM (AOL Instant Messenger) gateway browsing, 148-149 configuration, 148 message translation, 150-153 namespaces, 149 presence information, 150 user registration, 149-150 AIML (Artificial Intelligence Markup Language) tag, 306 ECMAScript, 312-316 tag, 311 tag, 306 tag, 308-309 sample question-and-answer pair, 307 tag, 311 tag, 306 tag, 311 tag, 308 tag, 306 wild cards, 310 AIML folder, 302-303 tag, 49
Alicebot, 299-300 AIML (Artificial Intelligence Markup Language) tag, 306 ECMAScript, 312-316 tag, 311 tag, 306 tag, 308-309 sample question-and-answer pair, 307 tag, 311 tag, 306 tag, 311 tag, 308 tag, 306 wild cards, 310 AIML folder, 302-303 Alice-Jabber architecture, 316-318 Alicebot Web site, 440 AliceJabber class code listing, 318-321 getResponse() method, 322 receivedPacket() method, 321-323 configuration files, 303-305 ConnectionBean object, 323 defined, 300-301 design, 301 downloading, 302 goals, 300 lib folder, 303 listener registry, 324 running, 324-325 Web sites, 301 why it works, 301 AliceJabber class code listing, 318-321 getResponse() method, 322 receivedPacket() method, 321-323 aliceserver.jar file, 303 tag, 47-48
arbitrary data
alpha.xml file, 29-30 angle brackets (), 436 AOL Instant Messenger. See AIM gateway Apache server Apache monitoring client, 350-363 changedPresence() method, 362 checkData() method, 361 code listing, 352, 355-359 getStatusData() method, 360 http.conf configuration, 350-351 main processing loop, 359 receivedData() method, 362-363 sample messages, 352 sendData() method, 361 startJabber() method, 359-360 Web server status page, 351-352 Web site, 350 appendItem() method, 191 Apple Rendezvous, 370 applications Apache monitoring client, 350-363 changedPresence() method, 362 checkData() method, 361 code listing, 352, 355-359 getStatusData() method, 360 http.conf configuration, 350-351 main processing loop, 359 receivedData() method, 362-363 sample messages, 352 sendData() method, 361 startJabber() method, 359-360 Web server status page, 351-352 conversational applications, 7 cross-language chat client bind command, 418 buttonPress() method, 418
code listing, 415-417 initial chat window, 414-415 jid variable, 417 iksemel application, 408-409 code listing, 409-411 packets, 412-413 main() method, 411 my data structure, 411 packetRecv() method, 412 ImageViewer application, 99-100 Python version, 109-122 Ruby version, 100, 103-109 Jabber-Net C# application building, 407 code listing, 400-402 InitializeExcel() method, 404 InitializeJabber() method, 404 newIQ() method, 404 newMessage() method, 404 newPresence() method, 404 Presence() method, 404 UpdateExcel() method, 404 UpdatePresence() method, 404 Jabber-Net Visual Basic application building, 407 code listing, 404-406 traditional applications characteristics, 7-9 limitations of, 9-11 user creation scripts Java, 25-30 Python, 30-32 Applied Cryptography, 258 arbitrary data, sending, 99-100 Python application callback handlers, 119 ConnectToJabber() method, 119
How can we make this index more useful? Email us at
[email protected]
445
446
arbitrary data
Jabber library capabilities, 119 JabberHandler class, 118 jabberHandler.py, 115-117 jabberLogin.py, 114 messageCB() method, 121 pictureViewer.py, 110-114 presenceCB() method, 121 Process() method, 118 sendToRoster() method, 120 Ruby application, 100 Bitmapper.rb, 101-106 connectToJabber() method, 108-109 disconnectFromJabber() method, 109 ImageWindow class, 106-107 sendJabberImage() method, 107-108 architecture of Jabber, 123 asynchronous messaging, 19-20 browsable agents, 142-147 built-in services, 11-13 c2s service, 123 client-based services, 17-19 client/server architecture, 14-17, 274-275 client initialization authentication, 130-134 client/server connections, 130 fetching roster, 136-138 presence information, 138-141 querying for agents, 134-136 conference service, 124 decentralization, 21-22 dnsrv service, 124 extensibility, 20-21 Internet addressing, 23 intranet addressing, 24 instant messaging gateways browsing, 148-149 configuration, 148 message translation, 150-153
namespaces, 149 presence information, 150 user registration, 149-150 JSM (Jabber Session Manager), 123 JUD (Jabber User Directory), 124 logger services, 124 messaging client-to-client messages, 125-127 remote messaging, 127-129 open source licensing, 19, 33 presence information, 23 real-time messaging, 22-23 security, 22 servers/glass terminals, 273-274 service discovery, 13-14 s2s service, 124 TCP/IP, 22 three-tier architecture, 275 xdb (XML database), 124 XML, 22 Web architectures, 275-276 Artificial Intelligence Markup Language. See AIML asynchronous messaging, 19-20 attributes, 436 authenticate() method, 234-238, 242 authentication AuthBase class, 234-236 authenticate() method, 234-236 authentication modules, 233 clients, 130-134 custom authentication authentication service connection, 246 authorization packets, 247-249 code listing, 249-254 disableStreamHeader() method, 254 tag, 257 isValid() method, 256 jabber.cml file, 245-246
buttonPress() method
tag, 246 log() method, 256 login() method, 256 receivedPacket() method, 254 replyWithError() method, 255-257 replyWithLogin() method, 255 setup() method, 254 digest authentication, 238-242 authenticate() method, 242 code listing, 241-242 example, 239-240 SHA-1 algorithm, 239 get requests, 234 plain authentication authenticate() method, 238 code listing, 237-238 error packets, 237 packets, 236 mod_auth_plain module, 238 server-to-server connection authentication, 265-268 zero-knowledge authentication, 242-245 code listing, 244 packets, 243 mod_auth_Ok module, 242 authorization packets, 247-249 tag, 49 auth_result method, 420 AutoAgents property (JabberClient class), 400 AutoLogin property (JabberClient class), 400 automatic client registration, disabling, 231-232 AutoPresence property (JabberClient class), 400 AutoRoster property (JabberClient class), 400
available type attribute ( tag), 88 away value (presence), 89
B -B option (jabberd), 40 base 64 data type, 283 beans, ConnectionBean, 323 bind command, 418 Bitmapper.rb file, 101-106 tag, 56, 436 jabber:x:event attachments, 93-94 XML CDATA, 94-95 books, Applied Cryptography, 258 Boolean data type, 283 tag, 304 browsable agents, 142-147 browse requests, 169-170 tag, 13, 42, 144 browsing AIM (AOL Instant Messenger) Transport, 148-149 browse requests, 169-170 handleBrowse() method, 169-170 listDatabases() method, 171 listFields() method, 173 listTables() method, 172 list of columns, 173 list of databases, 171 list of tables, 172 services, 60 build() method, 192 built-in services library modules, 11 STDIO, 13 TCP/IP sockets, 11-12 buttonPress() method, 418
How can we make this index more useful? Email us at
[email protected]
447
448
-c option
C -c option (jabberd), 40 C# Jabber-Net sample application building, 407 code listing, 400-402 InitializeExcel() method, 404 InitializeJabber() method, 404 newIQ() method, 404 newMessage() method, 404 newPresence() method, 404 Presence() method, 404 UpdateExcel() method, 404 UpdatePresence() method, 404 C/C++ iksemel downloading, 408 installing, 408 sample application, 408-413 JECL (Jabber External Component Libraries), 408 sending messages from, 409-413 c2s (client-to-server) service, 48-49, 123 tag, 306 CDATA, 94-95 cert.pem file, 264 Certificate message (SSL), 258 certificates, 258 changedPresence() method, 362 chat Alicebot, 299-300 AIML (Artificial Intelligence Markup Language), 306-316 AIML folder, 302-303 Alice-Jabber architecture, 316-318 Alice Java code tree, 317-318 Alicebot Web site, 440 AliceJabber class, 318-323
configuration files, 303-305 ConnectionBean object, 323 defined, 300-301 design, 301 downloading, 302 goals, 300 lib folder, 303 listener registry, 324 running, 324-325 Web sites, 301 why it works, 301 conference service, 63-66, 124 cross-language example bind command, 418 buttonPress method, 418 code listing, 415-417 initial chat window, 414-415 jid variable, 417 chat type attribute ( tag), 93 chat value (presence), 89 chatterbot-based applications (Alicebot), 299-300 AIML (Artificial Intelligence Markup Language) tag, 306 ECMAScript, 312-316 tag, 311 tag, 306 tag, 308-309 sample question-and-answer pair, 307 tag, 311 tag, 306 tag, 311 tag, 308 tag, 306 wild cards, 310 AIML folder, 302-303
clients
Alice-Jabber architecture, 316-318 Alice Java code tree, 317-318 Alicebot Web site, 440 AliceJabber class code listing, 318-321 getResponse() method, 322 receivedPacket() method, 321-323 configuration files, 303-305 ConnectionBean object, 323 defined, 300-301 design, 301 downloading, 302 goals, 300 lib folder, 303 listener registry, 324 running, 324-325 Web sites, 301 why it works, 301 checkData() method, 361 checkPendingMessages() method, 393-394 classes AliceJabber code listing, 318-321 getResponse() method, 322 receivedPacket() method, 321-323 AuthBase, 234-236 ConnectionBean, 230 ConnectionBeanSSL, 263 DiscoveryService, 376 ExecThread, 347-348 ImageWindow, 106-107 InfoQueryBuilder, 28 JabberClient, 399-400 JabberHandler, 118 Message, 376 NewUser creating in Java, 27 creating in Python, 31
PeerGroup, 376 PeerGroupFactory, 376 PipeService, 376 ReaderThread, 335-336 ReportData, 189-190 ReportDataBuilder, 191-192 ReportDataHandler, 192-193 XDBBuilder, 194 client-based services, 17-19 client-to-server (c2s) service, 48-49, 123 client/server architecture, 14-17, 274-275 ClientChangeCipherSpec message (SSL), 259 ClientFinished message (SSL), 259 ClientHello message (SSL), 258 ClientKeyExchange message (SSL), 259 clients, 69-70. See also users Apache monitoring client, 350-363 changedPresence() method, 362 checkData() method, 361 code listing, 352, 355-359 getStatusData() method, 360 httpd.conf configuration, 350-351 main processing loop, 359 receivedData() method, 362-363 sample messages, 352 sendData() method, 361 startJabber() method, 359-360 Web server status page, 351-352 authentication, 130-134 AuthBase class, 234, 236 authenticate() method, 234, 236 authentication modules, 233 custom authentication, 245-257 digest authentication, 238-242
How can we make this index more useful? Email us at
[email protected]
449
450
clients
get requests, 234 plain authentication, 236-238 zero-knowledge authentication, 242-245 c2s service, 48-49, 123 client-based services, 17-19 client/server architecture, 14-17, 274-275 connecting to Jabber server, 130 conversational streams, 71-72 cross-language chat client bind command, 418 buttonPress method, 418 code listing, 415-417 initial chat window, 414-415 jid variable, 417 defined, 70 error codes, 81-82 tag, 90 initialization authentication, 130-134 client/server connections, 130 fetching roster, 136-138 presence information, 138-141 querying for agents, 134-136 inventory management service InvComponent.py, 200-207 rssAccount.py, 224 rssClient.py, 223 rssComponent.py, 216-221 tag, 78 from attribute, 79 id attribute, 79 namespaces, 82-83 to attribute, 79 type attribute, 79-82 Jabber-to-JXTA bridge addDiscoveryListener() method, 388 advertisement discovery, 392-393
checkPendingMessages() method, 393-394 client initialization, 389-390 code library initialization, 387-388 code listing, 380-387 discover message, 376-377 discoveryEvent() method, 388, 392-393 flushAdvertisements() method, 389 getLocalAdvertisements() method, 389-391 getPipeService() method, 389 getRemoteAdvertisements() method, 388, 391 groups message, 376 main() method, 387 messages, receiving, 396 messages, sending, 393-395 newNetPeerGroup() method, 388 peer enumeration, 391-392 peers message, 376 pipeMsgEvent() method, 396 receivedPacket() method, 390-392 sendAndWait() method, 390 sendToAll() method, 393-394 sendToPipe() method, 394-395 startJabber() method, 389-390 startJxta() method, 387-388 talk command, 378-379 talk pipe advertisement, 379-380 JXTA initialization, 389-390 tag subelement, 93-95 subelement, 96 from attribute, 92 id attribute, 92 subelement, 95 subelement, 91, 96
code listings
to attribute, 92 type attribute, 92-93 subelement, 96-98 multiple clients, 71-72 namespaces, 83 jabber:x:autoupdate, 97 jabber:x:delay, 97 jabber:x:encrypted, 97 jabber:x:oob, 98 jabber:x:roster, 98 online resources, 439 presence information, 89, 138-141 tag, 84-89 tag, 90 querying for agents, 134-136 reallySimpleXMLRPCInvoker.py, 278-279 registration, 57-58, 226 disabling automatic registration, 231-232 inventory management service, 214, 221 result packets, 227 new user registration example, 227-231 registration data, 232 registration requests, 226-227 reports service, 186-188, 193-194 session mechanics, 70 sharing pictures between, 99-100 Python application, 109-122 Ruby application, 100, 103-109 tag, 89 simple Telnet script, 73-77 messages, 74-75 messages, 76-77 messages, 76 tag, 89 tag, 78
Tkabber, 418 user rosters, 136-138 welcome messages, 58 code listings Alicebot application AliceJabber class, 318-321 ECMAScript, 314-315 Apache monitoring client, 352-359 AuthBase class, 235-236 client registration, 227-229 cross-language application, 416-417 custom authentication service, 249-254 database service browse requests, 169 connecting to server, 166 listing databases, 171 search instructions, 175-176 search requests, 174-175 searching databases, 177-178 source code, 158-165 digest authentication, 241-242 iksemel application, 409-411 inventory management service InvComponent.py, 201-207 inventoryClient.py, 207 rssAccount.py, 224 rssClient.py, 223 rssComponent.py, 216-221 Jabber-Net C# sample application, 400-402 Visual Basic sample application, 404-406 jabber.xml file c2s configuration, 48 dnserv configuration, 51 elogger configuration, 52 io allow/deny settings, 47
How can we make this index more useful? Email us at
[email protected]
451
452
code listings
JSM (Java Session Manager) configuration, 53-55 rlogger configuration, 52 s2s configuration, 49-51 xdb configuration, 44-46 JabberPRCPeerA.py invoker, 288 JabberPRCPeerB.py service provider, 289-291 JabberPRCPeerC.py service provider, 293-294 JXTA peer advertisement, 367-368 JXTA shell commands, 373-374 JXTA-Jabber client advertisement discovery, 393 client initialization, 390 code library initialization, 388 messages, receiving, 396 messages, sending, 393-395 peer enumeration, 391 source code, 380-387 method call XML encoding, 281 method response XML encoding, 282 NewUser.java, 25-26 newUser.py, 30 plain authentication, 237-238 Python ImageViewer application jabberHandler.py, 115-117 jabberLogin.py, 114 pictureViewer.py, 110-114 reallySimpleXMLRPCInvokee.py, 280 reallySimpleXMLRPCInvoker.py, 278 reports service client registration, 194 generateReports() method, 196-197 handleSearchResult() method, 197 ReportData class, 190 ReportData handler configuration, 193 ReportDataBuilder class, 191 ReportDataHandler class, 192-193 source code, 180-186
rssComponent.py, 216 Ruby ImageView Bitmapper.rb, 101-106 connectToJabber() method, 108 disconnectFromJabber() method, 109 ImageWindow class, 106-107 sendJabberImage() method, 107-108 system control script, 344-347 system logger service, 330-335 version requests, responding to, 340 WSDL (Web Services Description Language), 285 XML-RPC wrapped for Jabber transport, 295 zero-knowledge authentication, 244 columns, listing, 173 command-line options (jabberd), 40 commands bind, 418 configure, 36-37, 260 jabberd, 40 JXTA commands, 372-375 list of, 373-374 man, 372 peers, 374 talk, 375, 378-379 make, 37-38, 260 make test, 38 netstat, 44, 263 rpm, 260 comments, 436-437 components, 18 Composing value (jabber:x:events attachments), 94 conference service, 63-66, 124 configuration Alicebot, 303-305 c2s service, 48-49 conference service, 63-66
controlling sets of computers
database services, 157-158 dnsrv service, 51 elogger service, 52-53 io service, 46 tag, 47-48 tag, 47-48 tag, 46-47 tag, 46 tag, 48 Jabber server AIM (AOL Instant Messenger) gateway, 148 command-line options, 40 jabber.xml file, 40-42 jabber.xml file, 40-42 tag, 42 c2s service configuration, 48-49 conference service configuration, 63-66 custom authentication, 245-246 dnsrv service configuration, 51 tag, 41 inventory management service, 211 io service configuration, 46-48 tag, 41 JSM (Jabber Session Manager) configuration, 53-61 JUD (Jabber User Directory) configuration, 61-63 rlogger configuration, 51-53 s2s service configuration, 49-51 tag, 40 tag, 42 tag, 42 xdb configuration, 44-46 JSM (Jabber Session Manager) client registration, 57-58 client welcome message, 58 components, 60-61
default configuration, 53-55 filters, 56-57 server administration, 58-59 server vCard, 57 service browsing, 60 updates, 59 vCard synchronization, 59 JUD (Jabber User Directory), 61-63 JXTA, 371 rlogger service, 51-52 s2s service, 49-51 default configuration, 49-50 tag, 50-51 tag, 50 tag, 50 tag, 50 tag, 50 SSL (Secure Sockets Layer), 48, 260-265 WinJab, 66-68 xdb (XML database) custom configuration, 45-46 default configuration, 44-45 configure command, 36-37, 260 Connect() method, 400 connecting authentication services, 246 database service to server, 166-167 to Jabber server, 130 ConnectionBean class, 230, 323 ConnectionBeanSSL class, 263 connectToJabber() method, 108-109, 119, 419, 422-425 tag, 57 controlling sets of computers, 341-342 group messaging, 343 one-on-one messaging, 342-343 system control script, 344-350
How can we make this index more useful? Email us at
[email protected]
453
454
conversational agents
conversational agents (Alicebot), 299-300 AIML (Artificial Intelligence Markup Language) tag, 306 ECMAScript, 312-316 tag, 311 tag, 306 tag, 308-309 sample question-and-answer pair, 307 tag, 311 tag, 306 tag, 311 tag, 308 tag, 306 wild cards, 310 AIML folder, 302-303 Alice-Jabber architecture, 316-318 Alice Java code tree, 317-318 Alicebot Web site, 440 AliceJabber class code listing, 318-321 getResponse() method, 322 receivedPacket() method, 321-323 configuration files, 303-305 ConnectionBean object, 323 defined, 300-301 design, 301 downloading, 302 goals, 300 lib folder, 303 listener registry, 324 running, 324-325 Web sites, 301 why it works, 301 conversational applications, 7 cross-language chat client bind command, 418 buttonPress method, 418
code listing, 415-417 initial chat window, 414-415 jid variable, 417 cryptographic hashes, 238. See also digest authentication custom authentication authentication service connection, 246 authorization packets, 247-249 code listing, 249-254 disableStreamHeader() method, 254 tag, 257 isValid() method, 256 jabber.cml file, 245-246 tag, 246 log() method, 256 login() method, 256 receivedPacket() method, 254 replyWithError() method, 255-257 replyWithLogin() method, 255 setup() method, 254 custom packet handler, 188-189 ReportData class, 189-190 ReportData handler configuration, 193 ReportDataBuilder class, 191-192 ReportDataHandler class, 192-193 Cygwin Unix tools, 35
D -D option (jabberd), 40 data types, 282-283 database service, 156 browsing databases browse requests, 169-170 handleBrowse() method, 169-170 list of columns, 173 list of databases, 171 list of tables, 172
directories
listDatabases() method, 171 listFields() method, 173 listTables() method, 172 code listing, 158-165 configuring, 157-158 connecting to server, 166-167 JDBC (Java Database Connectivity), 167-168 searching databases getColumns() method, 176 getSearchInstructions() method, 175-176 handleSearch() method, 174-175 search form, 176-177 search instructions, 175-176 search requests, 174 search result packets, 178-179 search() method, 177-178 sendError() method, 175 databases browsing browse requests, 169-170 handleBrowse() method, 169-170 list of columns, 173 list of databases, 171 list of tables, 172 listDatabases() method, 171 listFields() method, 173 listTables() method, 172 database service, 156 browsing databases, 169-173 code listing, 158-165 configuring, 157-158 connecting to server, 166-167 JDBC (Java Database Connectivity), 167-168 searching databases, 174-179
JDBC (Java Database Connectivity), 167-168 getCatalogs() method, 168 getConnection() method, 167-168 getTables() method, 168 next() method, 168 ResultSet object, 168 listing, 171 searching code listing, 177-179 getColumns() method, 176 getSearchInstructions() method, 175 handleSearch() method, 174-175 search form, 176-177 search instructions, 175-176 search requests, 174-175 search result packets, 178-179 search() method, 177-178 sendError() method, 175 xdb (XML database), 44-46, 124 date data type, 283 dec attribute ( tag), 47 decentralization, 21-22 Delivered value (jabber:x:events attachments), 93 tag, 47-48 dialback authentication, 265-268 tag, 50-51 digest authentication, 238-242 authenticate() method, 242 code listing, 241-242 example, 239-240 SHA-1 algorithm, 239 tag, 241 directories jabber-1.4.2, 36 JUD (Jabber User Directory), 61-63, 124
How can we make this index more useful? Email us at
[email protected]
455
456
disableStreamHeader() method disableStreamHeader() method, 254 disabling automatic client registration, 231-232 disconnectedCB() method, 32 disconnectFromJabber() method, 109 discovering JXTA advertisements, 392-393 services, 13-14 discovery service, 369 discoveryEvent() method, 388, 392-393 DiscoveryService class, 376 tag, 340 Displayed value (jabber:x:events attachments), 93 distributed control, 341-342 group messaging, 343 one-on-one messaging, 342-343 system control script code listing, 344-347 ExecThread class, 347-348 packets, 349 messageCB() method, 348 tag, 349 packets, 349 response_suffix string, 347 run() method, 348 dnd value (presence), 89 DNS (Domain Name System). See dnsrv service dnsrv service, 51, 124 downloading Alicebot, 302 iksemel, 408 Jabber server, 35-36 JXTA, 370 dumps() method, 279, 289
E Easy Installer (JXTA), 370-371 ECMAScript, 312-316 edge peers (JXTA), 370 elementChars() method, 193 elogger service, 52-53, 124 enabling SSL (Secure Sockets Layer), 260-265 endpoints, 368 enumerating JXTA peers, 391-392 error codes, 81-82 tag, 56, 90, 96, 257 error type attribute tag, 93 tag, 89 error value ( tag), 81 Event Viewer, 328 events monitoring, 327 system logger service, 329-337 Unix syslog file, 328 Windows Event Viewer, 328 self.wait_handle event, 336 Excel spreadsheets, maintaining from Jabber C# application building, 407 code listing, 400-402 InitializeExcel() method, 404 InitializeJabber() method, 404 newIQ() method, 404 newMessage() method, 404 newPresence() method, 404 Presence() method, 404 UpdateExcel() method, 404 UpdatePresence() method, 404
get value
Visual Basic application building, 407 code listing, 404-406 ExecThread class, 347-348 Exodus Web site, 440 extensibility, 20-21 Extensible Markup Language. See XML tags
F files. See also code listings aliceserver.jar, 303 cert.pem, 264 global.xdb, 196 http.conf, 350-351 jabber.xml, 40-42 tag, 42 c2s service configuration, 48-49 conference service configuration, 63-66 custom authentication, 245-246 dnsrv service configuration, 51 tag, 41 inventory management service, 211 io service configuration, 46-48 tag, 41 JSM (Jabber Session Manager) configuration, 53-61 JUD (Jabber User Directory), 61-63 rlogger configuration, 51-53 s2s service configuration, 49-51 tag, 40 tag, 42 tag, 42 xdb configuration, 44-46 Jabberbeans.jar, 303 JabberD-1.4.2.exe, 36 js.jar, 303
key.pem, 262 mysql.jar, 303 org.mortbay.jetty.jar, 303 privkey.pem, 262 startup.xml (Alicebot), 303-305 syslog, 328 user1.xml, 232 user2.xml, 232 xerces.jar, 303 filters (JSM), 56-57 finding services, 13-14 Flash Messaging Libraries, 440 float data type, 282 flushAdvertisements() method, 389 folders AIML, 302-303 lib, 303 tag, 139 formatReport() method, 197 tag, 56 from attribute tag, 79 message tag, 92 presence tag, 88 route tag, 131, 135 tag, 56
G gateways, instant messaging gateways browsing, 148-149 configuration, 148 message translation, 150-153 namespaces, 149 presence information, 150 user registration, 149-150 generateReports() method, 196-197 get value ( tag), 80
How can we make this index more useful? Email us at
[email protected]
457
458
getCatalogs() method
getCatalogs() method, 168 getColumns() method, 176 getConnection() method, 167-168 getDiscoveryService() method, 376 getLocalAdvertisement() method, 376 getLocalAdvertisements() method, 389-391 getPipeService() method, 376, 389 getRemoteAdvertisement() method, 376 getRemoteAdvertisements() method, 388, 391 getResponse() method, 322 getSearchInstructions() method, 175 getStatusData() method, 360 getTables() method, 168 giveInstructions() method, 187 glass terminals, 274 global.xdb file, 196 goUnix() method, 337 goWin32() method, 336 graphics, sharing between clients. See ImageViewer application tag, 56 groupchat type attribute ( tag), 93
H -H option (jabberd), 40 handleBrowse() method, 169-170 handleRegistration() method, 188 handlers, KeyboardInterrupt, 210 handleSearch() method, 174-175 handleSearchResult() method, 197 handleXDB() method, 195 handshakes (SSL), 258 headers, stream headers, 438
headline messages, sending from C, 409-413 headline type attribute ( tag), 93 heartbeat attribute ( tag), 46 history of Web services client/server architecture, 274-275 servers/glass terminals, 273-274 Web architectures, 275 tag, 65 tag, 41 tag, 143 http.conf file, 350-351
I -i option (jabberd), 40 id attribute tag, 79 tag, 92 tag, 88 tag, 130 identifiers, URNs (uniform resource names), 366 tag, 50, 268 igCB() method, 32 iksemel downloading, 408 installing, 408 sample application, 408-409 code listing, 409-411 IQ packets, 412-413 main() method, 411 my data structure, 411 packetRecv() method, 412 images, sharing between clients. See ImageViewer application
inventory management service
ImageViewer application, 99-100 Python version callback handlers, 119 ConnectToJabber() method, 119 Jabber library capabilities, 119 JabberHandler class, 118 jabberHandler.py, 115-117 jabberLogin.py, 114 messageCB() method, 121 pictureViewer.py, 110-114 presenceCB() method, 121 Process() method, 118 sendToRoster() method, 120 Ruby version, 100 Bitmapper.rb, 101-106 connectToJabber() method, 108-109 disconnectFromJabber() method, 109 ImageWindow class, 106-107 sendJabberImage() method, 107-108 ImageWindow class, 106-107 inc attribute ( tag), 47 tag, 436 InfoQuery messages, 28 InfoQueryBuilder class, 28 init attribute ( tag), 46 tag, 49 init() method, 213 initCache() method, 195 initialization Jabber clients authentication, 130-134 client/server connections, 130 fetching roster, 136-138 presence information, 138-141 querying for agents, 134-136 JXTA-Jabber client, 389-390 JXTA libraries, 387-388
InitializeExcel() method, 404 InitializeJabber() method, 404 installation iksemel, 408 Jabber server Linux, 36-39 Unix, 36-39 Windows, 39 jabber4r, 425 JXTA, 370-371 Net::Jabber, 422 instant messaging gateways, 148-153 browsing, 148-149 configuration, 148 message translation, 150-153 namespaces, 149 presence information, 150 user registration, 149-150 WinJab, 66-68 tag, 144-145, 188 integer data type, 282 Internet addressing, 23 intranet addressing, 24 InvComponent.py, 200-207 inventory management service, 198-200 import statements, 208 init() method, 213 InvComponent.py, 200-207 inventoryClient.py, 207 messages, 208-210 iqCB() method, 209 isAvailable() method, 213 jabber.xml configuration, 211 jabberd connection, 212-213 messageCB() method, 211 pickled client list, 211-212
How can we make this index more useful? Email us at
[email protected]
459
460
inventory management service
process() method, 214 registration, 214, 221 rssAccount.py, 224 rssClient.py, 223 rssComponent.py, 216-221 setOnline() method, 213 setShow() method, 213 setStatus() method, 213 inventoryClient.py, 207 invokees (XML-RPC), 280 invokers (XML-RPC) JabberRPCPeerA.py invoker, 287-289 reallySimpleXMLRPCInvoker.py client, 278 io service configuration, 46-48 tag, 47-48 tag, 47-48 tag, 46-47 tag, 46 tag, 48 tag, 262 tag, 49-50, 262 tag, 20-21, 78, 133, 338, 349 authentication get requests, 234 browse requests, 169-170 client registration, 227 from attribute, 79 id attribute, 79 inventory management service, 208-210 namespaces, 82-83 plain authentication, 236-237 reports service registration, 187 search requests, 174-175 simple Telnet example, 74-75 to attribute, 79 type attribute, 79-82 zero-knowledge authentication, 243 iqCB() method, 209
isAvailable() method, 213 isValid() method, 256 tag, 147
J jabber-1.4.2 directory, 36 Jabber architecture asynchronous messaging, 19-20 built-in services library modules, 11 STDIO, 13 TCP/IP sockets, 11-12 client-based services, 17-19 client/server interaction, 14-17 decentralization, 21-22 extensibility, 20-21 Internet addressing, 23 intranet addressing, 24 open source licensing, 19, 33 presence information, 23 real-time messaging, 22-23 security, 22 service discovery, 13-14 TCP/IP, 22 XML, 22 Jabber clients. See clients Jabber External Component Libraries (JECL), 408 Jabber, Inc. Web site, 33 Jabber Messages message (SSL), 259 Jabber-Net building applications, 407 C# sample application building, 407 code listing, 400-402 InitializeExcel() method, 404 InitializeJabber() method, 404
Jabber server
newIQ() method, 404 newMessage() method, 404 newPresence() method, 404 Presence() method, 404 UpdateExcel() method, 404 UpdatePresence() method, 404 Connect() method, 400 JabberClient class, 399-400 Visual Basic sample application, 404-407 Web site, 399 Jabber-RPC, 441 Jabber server, 123, 155-156. See also services administration, 58-59 Alicebot, 299-300 AIML (Artificial Intelligence Markup Language), 306-316 AIML folder, 302-303 Alice-Jabber architecture, 316-318 Alice Java code tree, 317-318 Alicebot Web site, 440 AliceJabber class, 318-323 configuration files, 303-305 ConnectionBean object, 323 defined, 300-301 design, 301 downloading, 302 goals, 300 lib folder, 303 listener registry, 324 running, 324-325 Web sites, 301 why it works, 301 browsable agents, 142-147 client initialization authentication, 130-134 client/server connections, 130
fetching roster, 136-138 presence information, 138-141 querying for agents, 134-136 client/server interaction, 14-17 configuration AIM (AOL Instant Messenger) gateway, 148 command-line options, 40 jabber.xml file, 40-42 multiple virtual Jabber servers, 41 database service, 156 browsing databases, 169-173 code listing, 158-165 configuring, 157-158 connecting to server, 166-167 JDBC (Java Database Connectivity), 167-168 searching databases, 174-179 dnsrv service, 124 downloading, 35-36 installation Linux, 36-39 Unix, 36-39 Windows, 39 instant messaging gateways, 148-153 inventory management service, 198-200 import statements, 208 init() method, 213 InvComponent.py, 200-207 inventoryClient.py, 207 messages, 208-210 iqCB() method, 209 isAvailable() method, 213 jabber.xml configuration, 211 jabberd connection, 212-213 messageCB() method, 211 pickled client list, 211-212
How can we make this index more useful? Email us at
[email protected]
461
462
Jabber server
process() method, 214 registration, 214, 221 rssAccount.py, 224 rssClient.py, 223 rssComponent.py, 216-221 setOnline() method, 213 setShow() method, 213 setStatus() method, 213 messaging client-to-client messages, 125-127 remote messaging, 127-129 online resources, 439 reports service, 179-180 code listing, 180-186 registration, 186-188, 193-194 ReportData class, 189-190 ReportData handler configuration, 193 ReportDataBuilder class, 191-192 ReportDataHandler class, 192-193 timed report generation, 196-198 XDB custom storage, 193-196 s2s service, 49-51, 124 server-to-server connection authentication, 265-268 starting, 43-44 stopping, 43 troubleshooting, 43-44 vCards configuration, 57 synchronization, 59 Jabber session manager. See JSM Jabber Software Foundation, 33 Jabber Studio Web site, 61, 123, 439 Jabber-to-JXTA bridge addDiscoveryListener() method, 388 advertisement discovery, 392-393 checkPendingMessages() method, 393-394
client initialization, 389-390 code library initialization, 387-388 code listing, 380-387 discover message, 376-377 discoveryEvent() method, 388, 392-393 flushAdvertisements() method, 389 getLocalAdvertisements() method, 389-391 getPipeService() method, 389 getRemoteAdvertisements() method, 388, 391 groups message, 376 main() method, 387 messages, receiving, 396 messages, sending queued messages, 394 sendToPipe() method, 394-395 to everyone on roster, 393 newNetPeerGroup() method, 388 peer enumeration, 391-392 peers message, 376 pipeMsgEvent() method, 396 receivedPacket() method, 390, 392 sendAndWait() method, 390 sendToAll() method, 393-394 sendToPipe() method, 394-395 startJabber() method, 389-390 startJxta() method, 387-388 talk command, 378-379 talk pipe advertisement, 379-380 Jabber User Directory (JUD), 61-63, 124 jabber.py library, 440-441 jabber.xml file, 40-42 tag, 42 c2s service configuration, 48-49 conference service configuration, 63-66 custom authentication, 245-246
JECL
dnsrv service configuration, 51 tag, 41 inventory management service, 211 io service configuration, 46-48 tag, 41 JSM (Jabber Session Manager) configuration client registration, 57-58 client welcome messages, 58 components, 60-61 default configuration, 53-55 filters, 56-57 server administration, 58-59 service browsing, 60 updates, 59 vCards, 57-59 JUD (Jabber User Directory) configuration, 61-63 rlogger configuration, 51-53 s2s service configuration, 49-51 tag, 40 tag, 42 tag, 42 xdb configuration, 44-46 jabber4r, 424-427 connectToJabber() method, 425 installing, 425 messageCB() method, 426 rosterCB() method, 427 sendMsg() method, 425 sendPresence() method, 426 subscriptionCB() method, 426-427 JabberBeans, 316, 413-414, 440 Jabberbeans.jar file, 303 JabberClient class, 399-400 JabberD-1.4.2.exe file, 36 jabberd command, 40 jabberd. See Jabber server
tag, 41 JabberHandler class, 118 jabberHandler.py file, 115-117 Jabberlib, 418-421 auth_result() method, 420 connectToJabber() method, 419 roster_cmd() method, 420 sendMsg() method, 420 sendPresence() method, 421 jabberLogin.py file, 114 JabberPy, 414 JabberRPCPeerA.py invoker, 287-289 JabberRPCPeerB.py service provider, 289-292 JabberRPCPeerC.py service provider, 292-295 jabber:iq:register namespace, 187 jabber:iq:search namespace, 173-174 jabber:x:autoupdate namespace, 97 jabber:x:delay namespace, 97 jabber:x:encrypted namespace, 97 jabber:x:event namespace, 93-94 jabber:x:oob namespace, 98 jabber:x:roster namespace, 98 jar files, 303 Java Binding API, 375-376 Java Database Connectivity. See JDBC Java scripts, user creation script alpha.xml file, 29-30 NewUser class, 27 NewUser.bat file, 26 NewUser.java code listing, 25-26 user registration, 27-28 tag, 311 JDBC (Java Database Connectivity), 167-168 JECL (Jabber External Component Libraries), 408
How can we make this index more useful? Email us at
[email protected]
463
464
jid variable
jid variable, 417 JID() method, 193 js.jar file, 303 JSM (Jabber Session Manager), 41, 123 client registration, 57-58 client welcome message, 58 components, 60-61 default configuration, 53-55 filters, 56-57 server administration, 58-59 server vCard, 57 service browsing, 60 updates, 59 vCard synchronization, 59 tag, 246 JUD (Jabber User Directory), 61-63, 124 JXTA, 365-366 advertisements discovering, 392-393 peer advertisements, 366-368 talk pipe advertisements, 379-380 classes, 375-376 client initialization, 389-390 commands list of, 373-374 man, 372 peers, 374 talk, 375, 378-379 configuration, 371 discovery service, 369 downloading, 370 installation, 370-371 Jabber-to-JXTA bridge addDiscoveryListener() method, 388 advertisement discovery, 392-393 checkPendingMessages() method, 393-394
client initialization, 389-390 code library initialization, 387-388 code listing, 380-387 discover message, 376-377 discoveryEvent() method, 388, 392-393 flushAdvertisements() method, 389 getLocalAdvertisements() method, 389-391 getPipeService() method, 389 getRemoteAdvertisements() method, 388, 391 groups message, 376 main() method, 387 messages, receiving, 396 messages, sending, 393-395 newNetPeerGroup() method, 388 peer enumeration, 391-392 peers message, 376 pipeMsgEvent() method, 396 receivedPacket() method, 390-392 sendAndWait() method, 390 sendToAll() method, 393-394 sendToPipe() method, 394-395 startJabber() method, 389-390 startJxta() method, 387-388 talk command, 378-379 talk pipe advertisement, 379-380 Java Binding API, 375-376 membership service, 369 messages, 368 receiving, 396 sending, 393-395 modules, 369 peer groups, 369 peers edge peers, 370 endpoints, 368
log() method
enumerating, 391-392 peer groups, 369 proxy peers, 370 relay peers, 370 rendezvous peers, 370 pipe service, 369 propogate pipes, 369 resolver service, 369 services, 369 URNs (uniform resource names), 366 Web site, 370
K-L tag, 46-47 key.pem file, 262 KeyboardInterrupt handler, 210 tag, 305 lib folder, 303 libraries, 11 iksemel downloading, 408 installing, 408 sample application, 408-413 Jabber-Net, 399 building applications, 407 C# sample application, 400-404, 407 Connect() method, 400 JabberClient class, 399-400 Visual Basic sample application, 404-407 Web site, 399 jabber.py, 440-441 jabber4r, 424-427 connectToJabber() method, 425 installing, 425 messageCB() method, 426
rosterCB() method, 427 sendMsg() method, 425 sendPresence() method, 426 subscriptionCB() method, 426-427 JabberBeans, 413-414 JabberBeans Library, 316 Jabberlib, 418-421 JabberPy, 414 JECL (Jabber External Component Libraries), 408 JXTA libraries, 387-388 Net::Jabber, 421-424 connectToJabber method, 422-423 installing, 422 messageCB method, 423 pollJabber method, 423 presenceCB method, 424 sendMsg method, 423 sendPresence method, 424 online resources, 440 licensing, open source, 19, 33 Linux, installing Jabber server on, 36-39 configure command, 36-37 make command, 37-38 listDatabases() method, 171 listen() method, 17 tag, 304 listFields() method, 173 listing columns, 173 databases, 171 tables, 172 listTables() method, 172 tag, 45 loads() method, 289 tag, 124 log() method, 256
How can we make this index more useful? Email us at
[email protected]
465
466
logging
logging, 124 elogger, 52-53 rlogger, 51-52 system events, 327 system logger service, 329-337 Unix syslog file, 328 Windows Event Viewer, 328 login() method, 256 tag, 53
M Macromedia Flash Messaging Libraries, 440 main() method, 387 iksemel application, 411 user creation script, 27 make command, 37-38, 260 make test command, 38 man command, 372 management. See administration max attribute ( tag), 46 tag, 45 tag, 50 tag, 65 membership service, 369 Message class, 376 tag, 91-92, 125, 436 subelement jabber:x:event attachments, 93-94 XML CDATA, 94-95 subelement, 96 from attribute, 92 id attribute, 92 simple Telnet example, 76-77 subelement, 95 subelement, 91, 96 to attribute, 92
type attribute, 92-93 subelement, 96-98 messageCB() method, 32, 121, 211, 348, 423, 426 messaging See also tags (XML) Apache monitoring client, 352 asynchronous messaging, 19-20 client welcome messages, 58 client-to-client messages, 125-127 InfoQuery messages, 28 instant messaging browsing, 148-149 configuration, 148 gateways, 148-153 message translation, 150-153 namespaces, 149 presence information, 150 user registration, 149-150 jabber:x:event attachments, 93-94 JXTA messages, 368 receiving, 396 sending, 393-395 presence messages, 23, 76-77 real-time messaging, 22-23 remote messaging, 127-129 sending from C, 409-413 cross-language example, 414-418 iksemel application, 408-413 Jabber-Net C# application, 400-404, 407 Jabber-Net Visual Basic application, 404-407 jabber4r, 424-427 JabberBeans, 413-414 Jabberlib, 418-421 Net::Jabber, 421-424 simple Telnet script, 73-77
methods
sharing pictures between clients, 99-100 Python application, 109, 111-122 Ruby application, 100, 103-109 SSL (Secure Sockets Layer) Certificate, 258 ClientChangeCipherSpec, 259 ClientFinished, 259 ClientHello, 258 ClientKeyExchange, 259 Jabber Messages, 259 ServerChangeCipherSpec, 259 ServerFinished, 259 ServerHello, 258 ServerHelloDone, 258 WinJab, 66-68, 263-265 XML namespaces, 12, 20 tag, 282 methods addDiscoveryListener(), 388 appendItem(), 191 authenticate(), 234-238, 242 auth_result(), 420 build(), 192 buttonPress(), 418 changedPresence(), 362 checkData(), 361 checkPendingMessages(), 393-394 Connect(), 400 connectToJabber(), 108-109, 119, 419, 422-425 disableStreamHeader(), 254 disconnectedCB(), 32 disconnectFromJabber(), 109 discoveryEvent(), 388, 392-393 dumps(), 279, 289 elementChars(), 193 flushAdvertisements(), 389 formatReport(), 197
generateReports(), 196-197 getCatalogs(), 168 getColumns(), 176 getConnection(), 167-168 getDiscoveryService(), 376 getLocalAdvertisement(), 376 getLocalAdvertisements(), 389-391 getPipeService(), 376, 389 getRemoteAdvertisement(), 376 getRemoteAdvertisements(), 388, 391 getResponse(), 322 getSearchInstructions(), 175 getStatusData(), 360 getTables(), 168 giveInstructions(), 187 goUnix(), 337 goWin32(), 336 handleBrowse(), 169-170 handleRegistration(), 188 handleSearch(), 174-175 handleSearchResult(), 197 handleXDB(), 195 igCB(), 32 initCache(), 195 InitializeExcel(), 404 InitializeJabber(), 404 iqCB(), 209 isValid(), 256 JID(), 193 listDatabases(), 171 listen(), 17 listFields(), 173 listTables(), 172 loads(), 289 log(), 256 login(), 256 main(), 387 iksemel application, 411 user creation script, 27
How can we make this index more useful? Email us at
[email protected]
467
468
methods
messageCB(), 32, 121, 211, 348, 423, 426 newAdvertisement(), 376 newIQ(), 404 newMessage(), 404 newNetPeerGroup(), 376, 388 newPresence(), 404 next(), 168 packetRecv(), 412 pipeMsgEvent(), 396 pollJabber(), 423 Presence(), 404 presenceCB(), 32, 121, 424 Process(), 118, 214 public boolean register(), 27 ReadEventLog(), 337 receivedData(), 362-363 receivedPacket(), 230-231, 254, 321-323, 390-392 registerNewUser(), 31 replyWithError(), 255-257 replyWithLogin(), 255 ReportTask(), 196 rosterCB(), 427 roster_cmd(), 420 ROT13(), 280, 291-292 run(), 336, 348 search(), 177-178 sendAndWait(), 390 sendData(), 361 sendError(), 175 sendEvent(), 337 sendJabberImage(), 107-108 sendMsg(), 420, 423-425 sendPresence(), 421, 424-426 sendToAll(), 393-394 sendToPipe(), 394-395 set(), 28 setup(), 254 startJabber(), 359-360, 389-390 startJxta(), 387-388
stopHandler(), 193 subscriptionCB(), 426-427 UpdateExcel(), 404 UpdatePresence(), 404 mod_admin module, 60 mod_agents module, 60 mod_announce module, 60 mod_auth_digest module, 60, 233 mod_auth_Ok module, 60, 233, 242 mod_auth_plain module, 60, 233, 238 mod_browse module, 60 mod_echo module, 60 mod_filter module, 60 mod_last module, 60 mod_log module, 61 mod_offline module, 60 mod_presence module, 60 mod_register module, 61 mod_roster module, 60 mod_time module, 60 mod_vcard module, 60 mod_version module, 60 mod_xml module, 61 Model-View-Controller architecture, 274 modules JXTA, 369 library modules, 11 mod_admin, 60 mod_agents, 60 mod_announce, 60 mod_auth_digest, 60, 233 mod_auth_Ok, 60, 233, 242 mod_auth_plain, 60, 233, 238 mod_browse, 60 mod_echo, 60 mod_filter, 60 mod_last, 60
next() method
mod_log, 61 mod_offline, 60 mod_presence, 60 mod_register, 61 mod_roster, 60 mod_time, 60 mod_vcard, 60 mod_version, 60 mod_xml, 61 monitoring Apache monitoring client, 350-363 changedPresence() method, 362 checkData() method, 361 code listing, 352, 355-359 getStatusData() method, 360 http.conf configuration, 350-351 main processing loop, 359 receivedData() method, 362-363 sample messages, 352 sendData() method, 361 startJabber() method, 359-360 Web server status page, 351-352 system events, 327 system logger service, 329-337 Unix syslog file, 328 Windows Event Viewer, 328 multiple clients, serving, 71-72 multiple virtual Jabber servers, 41 multiplexors, 301 my data structure, 411 mysql.jar file, 303
N tag, 338 namespaces, 12, 20, 437 AIM (AOL Instant Messenger) Transport, 149 in tags, 82-83
jabber:iq:register, 187 jabber:iq:search, 173-174 jabber:x:autoupdate, 97 jabber:x:delay, 97 jabber:x:encrypted, 97 jabber:x:event, 93-94 jabber:x:oob, 98 jabber:x:roster, 98 table of, 83 .NET environment, Jabber-Net, 399 building applications, 407 C# sample application, 400-404, 407 Connect() method, 400 JabberClient class, 399-400 Visual Basic sample application, 404-407 Web site, 399 netstat command, 44, 263 Net::Jabber, 421-424 connectToJabber() method, 422-423 installing, 422 messageCB() method, 423 pollJabber() method, 423 presenceCB() method, 424 sendMsg() method, 423 sendPresence() method, 424 newAdvertisement() method, 376 newIQ() method, 404 newMessage() method, 404 newNetPeerGroup() method, 376, 388 newPresence() method, 404 NewUser class creating in Java, 25-27 creating in Python, 31 NewUser.bat file, 26 NewUser.java file, 25-26 newUser.py file, 30 next() method, 168
How can we make this index more useful? Email us at
[email protected]
469
470
tag
tag, 65, 349 normal value (presence), 89 tag, 65 ns attribute ( tag), 126 tag, 14, 56, 142, 149 numbers, error codes, 81-82
O objects ConnectionBean, 323 ResultSet, 168 tag, 56 Offline value (jabber:x:events attachments), 93 online resources. See also Web sites Alicebot, 440 Flash Messaging Libraries, 440 Jabber clients, 439 Jabber libraries, 440 Jabber protocol, 439 Jabber servers, 439 Jabber-RPC, 441 jabber.py library, 440-441 JabberBeans, 440 miscellaneous resources, 441 XML Cooktop, 441 open source licensing, 19, 33 OpenSSL. See SSL (Secure Sockets Layer) org.mortbay.jetty.jar file, 303 tag, 338 over-the-wire packaging (XML-RPC), 282
P packetRecv() method, 412 packets. See messages; tags
tag, 282 tag, 282 Password property (JabberClient class), 400 tag, 306 peer groups, 369 peer-a Jabber RPC invoker, 287-289 peer-b Jabber RPC service provider, 289-292 peer-c Jabber RPC service provider, 293-294 PeerGroup class, 376 PeerGroupFactory class, 376 peers (JXTA) edge peers, 370 endpoints, 368 enumerating, 391-392 peer advertisements, 366-368 peer groups, 369, 376 proxy peers, 370 relay peers, 370 rendezvous peers, 370 peers command, 374 penalty attribute ( tag), 47 Perl, Net::Jabber, 421-424 connectToJabber() method, 422-423 installing, 422 messageCB() method, 423 pollJabber() method, 423 presenceCB() method, 424 sendMsg() method, 423 sendPresence() method, 424 pictures, sharing between clients. See ImageViewer application pictureViewer.py file, 110-114 pipe service, 369 pipeMsgEvent() method, 396 pipes, 369, 376, 396 PipeService class, 376
protocols
plain authentication, 236-238 authenticate() method, 238 code listing, 237-238 error packets, 237 packets, 236 mod_auth_plain module, 238 pollJabber() method, 423 Port property (JabberClient class), 400 presence information, 23 AIM (AOL Instant Messenger) users, 150 away, 89 chat, 89 client presence information, 138-141 dnd, 89 normal, 89 tag, 84-87, 349 subelement, 90 from attribute, 88 id attribute, 88 subelement, 90 show subelement, 89 simple Telnet example, 76 subelement, 89 to attribute, 88 type attribute, 88-89 xa (extended away), 89 tag, 84-87, 349 subelement, 90 from attribute, 88 id attribute, 88 subelement, 90 show subelement, 89 simple Telnet example, 76 subelement, 89 to attribute, 88 type attribute, 88-89 Presence() method, 404
presenceCB() method, 32, 121, 424 tag, 90 tag, 65 tag, 65 privkey.pem file, 262 probe type attribute ( tag), 88 Process() method, 118, 214 propogate pipes, 369 protocols DNS (Domain Name System), 51, 124 JXTA, 365-366. See also Jabber-toJXTA bridge advertisements, 366-368, 379-380 classes, 375-376 commands, 372-375 configuration, 371 discovery service, 369 downloading, 370 installation, 370-371 Java Binding API, 375-376 membership service, 369 messages, 368 modules, 369 peer groups, 369 peers, 368-370 pipe service, 369 pipes, 369 resolver service, 369 services, 369 URNs (uniform resource names), 366 Web site, 370 SOAP (Simple Object Access Protocol), 283-284 TCP/IP (Transmission Control Protocol/Internet Protocol), 22 UDDI (Universal Description, Discovery, and Integration), 285
How can we make this index more useful? Email us at
[email protected]
471
472
protocols
Web services protocol stack, 277-278 WSDL (Web Services Description Language), 284-285 XML-RPC, 278-283 data types, 282-283 Jabber and, 286 Jabber-RPC object lessons, 296 JabberRPCPeerA.py invoker, 287-289 JabberRPCPeerB.py service provider, 289-292 JabberRPCPeerC.py service provider, 292-295 method call XML encoding, 281 method response XML encoding, 282 over-the-wire packaging, 282 reallySimpleXMLRPCInvokee.py service, 280 reallySimpleXMLRPCInvoker.py client, 278-279 transport mechanics, 279 XML-RPC wrapped for Jabber transport, 295 proxy peers, 370 public boolean register() method, 27 public key certificates, 258 tag, 65 Python ActiveState ActivePython, 330 ImageViewer application callback handlers, 119 ConnectToJabber() method, 119 Jabber library capabilities, 119 JabberHandler class, 118 jabberHandler.py, 115-117 jabberLogin.py, 114 messageCB() method, 121 pictureViewer.py, 110-114 presenceCB() method, 121
Process() method, 118 sendToRoster() method, 120 JabberPy, 414 user creation script callbacks, 32 NewUser class, 31 newUser.py code listing, 30 usage generator, 32 user registration, 31 Web site, 330 win32all extensions, 330
Q-R querying for agents, 134, 136 queued messages, sending, 394 tag, 50 tag, 308-309 tag, 46 ReaderThread class, 335-336 ReadEventLog() method, 337 real-time messaging, 22-23 reallySimpleXMLRPCInvokee.py service, 280 reallySimpleXMLRPCInvoker.py client, 278-279 tag, 436 receivedData() method, 362-363 receivedPacket() method, 230-231, 254, 321-323, 390-392 registerNewUser() method, 31 registration, 57-58 AIM (AOL Instant Messenger) users, 149-150 AliceJabber listener, 324 disabling automatic registration, 231-232 result packets, 227
resources
inventory management service, 214, 221 jabber:iq:register namespace, 187 Java, 27-28 new user registration example code listing, 227-229 ConnectionBean class, 230 output, 231 receivedPacket() method, 230-231 running, 230-231 Python, 31 registration data, 232 registration requests, 226-227 reports service, 186 code listing, 193-194 giveInstructions() method, 187 handleRegistration() method, 188 tag, 188 get/set packets, 187-188 relay peers, 370 remote messaging, 127-129 Remote Procedure Call (RPC). See XML-RPC Rendezvous (Apple OS X), 370 rendezvous peers, 370 tag, 57 replyWithError() method, 255, 257 replyWithLogin() method, 255 ReportData class, 189-190 ReportData handler, 193 ReportDataBuilder class, 191-192 ReportDataHandler class, 192-193 reports service, 179-180 code listing, 180-186 registration, 186 code listing, 193-194 giveInstructions() method, 187 handleRegistration() method, 188
tag, 188 get/set packets, 187-188 jabber:iq:register namespace, 187 ReportData class, 189-190 ReportData handler configuration, 193 ReportDataBuilder class, 191-192 ReportDataHandler class, 192-193 timed report generation, 196-198 formatReport() method, 197 generateReports() method, 196-197 handleSearchResult() method, 197 ReportTask() method, 196 XDB custom storage, 193-196 global.xdb file, 196 handleXDB() method, 195 initCache() method, 195 XDBBuilder class, 194 ReportTask() method, 196 requests browse requests, 169-170 registration requests, 226-227 search requests, 174-175 version information requests, 338-339 resetmeter attribute ( tag), 47 resolver service, 369 Resource property (JabberClient class), 400 tag, 56 resources. See also Web sites Alicebot, 440 Flash Messaging Libraries, 440 Jabber clients, 439 Jabber libraries, 440 Jabber protocol, 439 Jabber servers, 439 Jabber-RPC, 441 jabber.py library, 440-441
How can we make this index more useful? Email us at
[email protected]
473
474
resources
JabberBeans, 440 miscellaneous resources, 441 XML Cooktop, 441 responding to version requests, 339-341 response_suffix string, 347 restore attribute ( tag), 47 result value ( tag), 81 ResultSet object, 168 rlogger service, 51-52, 124 tag, 56 rosterCB() method, 427 rosters, returning, 136-138 roster_cmd() method, 420 ROT13 method, 280, 291-292 tag, 124, 128-137, 247-249 RPC (Remote Procedure Call). See XML-RPC rpm command, 260 rssAccount.py, 224 rssClient.py, 223 rssComponent.py, 216-221 Ruby ImageViewer application, 100 Bitmapper.rb, 101-106 connectToJabber() method, 108-109 disconnectFromJabber() method, 109 ImageWindow class, 106-107 sendJabberImage() method, 107-108 jabber4r, 424-427 connectToJabber() method, 425 installing, 425 messageCB() method, 426 rosterCB() method, 427 sendMsg() method, 425 sendPresence() method, 426 subscriptionCB() method, 426-427
run() method, 336, 348 running Alicebot, 324-325
S s2s (server-to-server) service, 124 default configuration, 49-50 tag, 50-51 tag, 50 tag, 50 tag, 50 tag, 50 Sash Web site, 441 scripts simple Telnet script, 73-77 messages, 74-75 messages, 76-77 messages, 76 user creation scripts Java, 25-30 Python, 30-32 search requests, 174-175 search() method, 177-178 searching databases code listing, 177-179 getColumns() method, 176 getSearchInstructions() method, 175 handleSearch() method, 174-175 search form, 176-177 search instructions, 175-176 search requests, 174-175 search result packets, 178-179 search() method, 177-178 sendError() method, 175 tag, 65 Secure Sockets Layer. See SSL
sending messages
security, 22, 225 client authentication AuthBase class, 234-236 authenticate() method, 234-236 authentication modules, 233 custom authentication, 245-257 digest authentication, 238-242 get requests, 234 plain authentication, 236-238 zero-knowledge authentication, 242-245 client registration disabling automatic registration, 231-232 result packets, 227 new user registration example, 227-231 registration data, 232 registration requests, 226-227 server-to-server connection authentication, 265-268 SSL (Secure Sockets Layer) Certificate message, 258 checking installation of, 260 ClientChangeCipherSpec message, 259 ClientFinished message, 259 ClientHello message, 258 ClientKeyExchange message, 259 configuration, 48 ConnectionBeanSSL class, 263 enabling, 260-265 handshake process, 258 Jabber Messages message, 259 public key certificates, 258 ServerChangeCipherSpec message, 259 ServerFinished message, 259 ServerHello message, 258 ServerHelloDone message, 258
self.wait_handle event, 336 sendAndWait() method, 390 sendData() method, 361 sendError() method, 175 sendEvent() method, 337 sending messages cross-language example, 414-418 from C, 409-413 iksemel application, 408-413 Jabber-Net C# application building, 407 code listing, 400-402 InitializeExcel() method, 404 InitializeJabber() method, 404 newIQ() method, 404 newMessage() method, 404 newPresence() method, 404 Presence() method, 404 UpdateExcel() method, 404 UpdatePresence() method, 404 Jabber-Net Visual Basic application building, 407 code listing, 404-406 jabber4r, 424-427 connectToJabber method, 425 installing, 425 messageCB method, 426 rosterCB method, 427 sendMsg method, 425 sendPresence method, 426 subscriptionCB method, 426-427 JabberBeans, 413-414 Jabberlib, 418-421 auth_result method, 420 connectToJabber method, 419 roster_cmd method, 420 sendMsg method, 420 sendPresence method, 421
How can we make this index more useful? Email us at
[email protected]
475
476
sending messages
Net::Jabber, 421 connectToJabber method, 422-423 installing, 422 messageCB method, 423 pollJabber method, 423 presenceCB method, 424 sendMsg method, 423 sendPresence method, 424 JXTA messages queued messages, 394 sendToPipe() method, 394-395 to everyone on roster, 393 simple Telnet script, 73-77 sendJabberImage() method, 107-108 sendMsg method, 420, 423-425 sendPresence method, 421, 424-426 sendToAll() method, 393-394 sendToPipe() method, 394-395 tag, 243 Server property (JabberClient class), 400 server-to-server connection authentication, 265-268 server-to-server service. See s2s service ServerChangeCipherSpec message, 259 ServerFinished message, 259 ServerHello message, 258 ServerHelloDone message, 258 servers, 123, 155-156, 273-274. See also services administration, 58-59 Alicebot, 299-300 AIML (Artificial Intelligence Markup Language), 306-316 AIML folder, 302-303 Alice-Jabber architecture, 316-318 Alice Java code tree, 317-318
Alicebot Web site, 440 AliceJabber class, 318-323 configuration files, 303-305 ConnectionBean object, 323 defined, 300-301 design, 301 downloading, 302 goals, 300 lib folder, 303 listener registry, 324 running, 324-325 Web sites, 301 why it works, 301 browsable agents, 142-147 client initialization authentication, 130-134 client/server connections, 130 fetching roster, 136-138 presence information, 138-141 querying for agents, 134-136 client/server interaction, 14-17 configuration AIM (AOL Instant Messenger) gateway, 148 command-line options, 40 jabber.xml file, 40-42 multiple virtual Jabber servers, 41 database service, 156 browsing databases, 169-173 code listing, 158-165 configuring, 157-158 connecting to server, 166-167 JDBC (Java Database Connectivity), 167-168 searching databases, 174-179 dnsrv service, 124 downloading, 35-36
services
installation Linux, 36-39 Unix, 36-39 Windows, 39 instant messaging gateways, 148-153 inventory management service, 198-200 import statements, 208 init() method, 213 InvComponent.py, 200-207 inventoryClient.py, 207 messages, 208-210 iqCB() method, 209 isAvailable() method, 213 jabber.xml configuration, 211 jabberd connection, 212-213 messageCB() method, 211 pickled client list, 211-212 process() method, 214 registration, 214, 221 rssAccount.py, 224 rssClient.py, 223 rssComponent.py, 216-221 setOnline() method, 213 setShow() method, 213 setStatus() method, 213 messaging client-to-client messages, 125-127 remote messaging, 127-129 online resources, 439 reports service, 179-180 code listing, 180-186 registration, 186-188, 193-194 ReportData class, 189-190 ReportData handler configuration, 193 ReportDataBuilder class, 191-192 ReportDataHandler class, 192-193
timed report generation, 196-198 XDB custom storage, 193-196 s2s service, 49-51, 124 server-to-server connection authentication, 265-268 starting, 43-44 stopping, 43 troubleshooting, 43-44 vCards configuration, 57 synchronization, 59 tag, 40, 64, 143 services, 11 advantages, 277 browsing, 60 c2s service, 48-49, 123 client-based services, 17-19 conference service, 63-66, 124 database service, 156 browsing databases, 169-173 code listing, 158-165 configuring, 157-158 connecting to server, 166-167 JDBC (Java Database Connectivity), 167-168 searching databases, 174-179 default configuration, 41 discovery service, 369 dnsrv service, 51, 124 elogger, 52-53, 124 finding, 13-14 history of, 276 client/server architecture, 274-275 servers/glass terminals, 273-274 Web architectures, 275 inventory management service, 198-200 import statements, 208 init() method, 213
How can we make this index more useful? Email us at
[email protected]
477
478
services
InvComponent.py, 200-207 inventoryClient.py, 207 messages, 208-210 iqCB() method, 209 isAvailable() method, 213 jabber.xml configuration, 211 jabberd connection, 212-213 messageCB() method, 211 pickled client list, 211-212 process() method, 214 registration, 214, 221 rssAccount.py, 224 rssClient.py, 223 rssComponent.py, 216-221 setOnline() method, 213 setShow() method, 213 setStatus() method, 213 io service, 46-48 JSM (Jabber Session Manager), 41, 53-61, 123 JUD (Jabber User Directory), 61-63, 124 JXTA, 369 library modules, 11 membership service, 369 pipe service, 369 protocol stack, 277-278 reports service, 179-180 code listing, 180-186 registration, 186-188, 193-194 ReportData class, 189-190 ReportData handler configuration, 193 ReportDataBuilder class, 191-192 ReportDataHandler class, 192-193 timed report generation, 196-198 XDB custom storage, 193-196 resolver service, 369 rlogger, 51-52, 124
s2s service, 49-51, 124 SOAP (Simple Object Access Protocol), 283-284 STDIO, 13 system logger service, 329-330 code listing, 330-335 goUnix() method, 337 goWin32() method, 336 ReaderThread class, 335-336 ReadEventLog() method, 337 run() method, 336 self.wait_handle event, 336 sendEvent() method, 337 TCP/IP sockets, 11-12 WinJab, 66-68 xdb (XML database), 44-46, 124 XML-RPC, 278-283 data types, 282-283 Jabber and, 286 Jabber-RPC object lessons, 296 JabberRPCPeerA.py invoker, 287-289 JabberRPCPeerB.py service provider, 289-292 JabberRPCPeerC.py service provider, 292-295 method call XML encoding, 281 method response XML encoding, 282 over-the-wire packaging, 282 reallySimpleXMLRPCInvokee.py service, 280 reallySimpleXMLRPCInvoker.py client, 278-279 transport mechanics, 279 XML-RPC wrapped for Jabber transport, 295 UDDI (Universal Description, Discovery, and Integration), 285 WSDL (Web Services Description Language), 284-285
tag
session manager. See JSM (Jabber Session Manager) sessions service. See JSM (Jabber Session Manager) set value ( tag), 80 set() method, 28 setOnline() method, 213 setShow() method, 213 setStatus() method, 213 tag, 57 setup() method, 254 SGML (Standard Generalized Markup Language), 435 SHA-1 algorithm, 239 sharing pictures between clients, 99-100 Python application callback handlers, 119 ConnectToJabber() method, 119 Jabber library capabilities, 119 JabberHandler class, 118 jabberHandler.py, 115-117 jabberLogin.py, 114 messageCB() method, 121 pictureViewer.py, 110-114 presenceCB() method, 121 Process() method, 118 sendToRoster() method, 120 Ruby application, 100 Bitmapper.rb, 101-106 connectToJabber() method, 108-109 disconnectFromJabber() method, 109 ImageWindow class, 106-107 sendJabberImage() method, 107-108 shell scripts. See scripts tag, 56, 86, 89 SOAP (Simple Object Access Protocol), 283-284
sockets. See SSL (Secure Sockets Layer) software, Cygwin Unix tools, 35 spreadsheets, maintaining from Jabber C# application building, 407 code listing, 400-402 InitializeExcel() method, 404 InitializeJabber() method, 404 newIQ() method, 404 newMessage() method, 404 newPresence() method, 404 Presence() method, 404 UpdateExcel() method, 404 UpdatePresence() method, 404 Visual Basic application building, 407 code listing, 404-406 SSL (Secure Sockets Layer) Certificate message, 258 checking installation of, 260 ClientChangeCipherSpec message, 259 ClientFinished message, 259 ClientHello message, 258 ClientKeyExchange message, 259 ConnectionBeanSSL class, 263 configuration, 48 enabling, 260-265 handshake process, 258 Jabber Messages message, 259 public key certificates, 258 ServerChangeCipherSpec message, 259 ServerFinished message, 259 ServerHello message, 258 ServerHelloDone message, 258 tag, 48-49, 262-263
How can we make this index more useful? Email us at
[email protected]
479
480
Standard Generalized Markup Language
Standard Generalized Markup Language (SGML), 435 starting Jabber server, 43-44 startJabber() method, 359-360, 389-390 startJxta() method, 387-388 startup.xml file (Alicebot), 303-305 stateful information, 276 tag, 86, 89 STDIO, 13 stopHandler() method, 193 stopping Jabber server, 43 tag, 78, 130 streams, 438 string data type, 282 strings response_suffix, 347 string data type, 282 structures, my data, 411 tag, 56, 95 subscribe type attribute ( tag), 88 subscribed type attribute ( tag), 89 subscriptionCB() method, 426-427 Suchman, Lucy, 9 synchronizing vCards, 59 syslog file, 328 system administration. See administration system control script code listing, 344-347 ExecThread class, 347-348 packets, 349 messageCB() method, 348 tag, 349 packets, 349 response_suffix string, 347 run() method, 348
system events, monitoring, 327 system logger service, 329-337 Unix syslog file, 328 Windows Event Viewer, 328 system logger service, 329-330 code listing, 330-335 goUnix() method, 337 goWin32() method, 336 ReaderThread class, 335-336 ReadEventLog() method, 337 run() method, 336 self.wait_handle event, 336 sendEvent() method, 337 tag, 311
T tables, listing, 172 tags (XML) , 436-437 , 49 , 47-48 attributes of, 436 , 49 , 56, 436 jabber:x:event attachments, 93-94 XML CDATA, 94 , 304 , 13, 42, 144 , 306 , 57 , 47-48 , 50-51 , 241 , 340 , 56, 90, 96, 257 , 139 , 56 , 56 , 56
tags
, 65 , 41 , 143 , 50, 268 , 436 , 49 , 144-145, 188 , 262 , 50, 262 , 20-21, 78, 133, 338, 349 authentication get requests, 234 browse requests, 169-170 client registration, 227 from attribute, 79 id attribute, 79 inventory management service, 208-210 namespaces, 82-83 plain authentication, 236-237 reports service registration, 187 search requests, 174-175 simple Telnet example, 74-75 to attribute, 79 type attribute, 79-82 zero-knowledge authentication, 243 , 147 , 41 , 311 , 246 , 46-47 , 305 , 304 , 45 , 124 , 53 , 45 , 50 , 65
, 125, 436 subelement, 93-95 subelement, 96 from attribute, 92 id attribute, 92 simple Telnet example, 76-77 subelement, 95 subelement, 91, 96 to attribute, 92 type attribute, 92-93 subelement, 96-98 , 282 , 338 , 65, 349 , 65 , 14, 56, 142, 149 , 56 , 338 , 282 , 282 , 306 , 84-87, 349 from attribute, 88 id attribute, 88 simple Telnet example, 76 to attribute, 88 type attribute, 88-89 , 90 , 65 , 65 , 65 , 50 , 308-309 , 46 , 436 , 57 , 56 , 56
How can we make this index more useful? Email us at
[email protected]
481
482
tags
, 124, 128-131, 133-137, 247-249 , 65 , 243 , 40, 64, 143 , 57 , 56, 86, 89 , 48-49, 262-263 , 86, 89 , 78, 130 , 56, 95 , 311 , 306 , 311 , 308 , 91, 96 , 45 , 243 , 306 , 56 , 340 , 56 , 42 , 340 , 282 , 42 , 338 , 96-98, 140 , 124-126, 132, 137-140 , 132 talk command (JXTA), 375, 378-379 talk pipe advertisements, 379-380 TCL, Jabberlib, 418-421 TCP/IP (Transmission Control Protocol/Internet Protocol), 11-12, 22 Telnet script, 73-77 messages, 74-75 messages, 76-77 messages, 76
tag, 306 tag, 311 tag, 308 tag, 91, 96 three-tier architecture, 274-275 timed report generation, 196-198 formatReport() method, 197 generateReports() method, 196-197 handleSearchResult() method, 197 ReportTask() method, 196 tag, 45 Tkabber client, 418 to attribute tag, 79 tag, 92 tag, 88 tag, 78, 130 tag, 126 tag, 243 tools, Cygwin Unix tools, 35 tag, 306 traditional applications characteristics, 7-9 limitations, 9-11 translating messages, 150-153 Transmission Control Protocol/Internet Protocol (TCP/IP), 11-12, 22 troubleshooting Jabber server, 43-44 type attribute tag, 79-82 tag, 92-93 tag, 88-89 tag, 126 tag, 56 tag, 340
tag
U -U option (jabberd), 40 UDDI (Universal Description, Discovery, and Integration), 285 tag, 56 unavailable type attribute ( tag), 88 Uniform Resource Identifiers (URIs), 437 uniform resource names (URNs), 366 Universal Description, Discovery, and Integration (UDDI), 285 Unix Cygwin Unix tools, 35 Jabber server installation configure command, 36-37 make command, 37-38 syslog file, 328 unsubscribe type attribute ( tag), 89 unsubscribed type attribute ( tag), 89 tag, 42 UpdateExcel() method, 404 UpdatePresence() method, 404 updating Jabber server, 59 URIs (Uniform Resource Identifiers), 437 URNs (uniform resource names), 366 User property (JabberClient class), 400 user1.xml file, 232 user2.xml file, 232 users. See also clients administrative users, 58-59 creating in Java alpha.xml file, 29-30 NewUser class, 27
NewUser.bat file, 26 NewUser.java code listing, 25-26 user registration, 27-28 creating with Python callbacks, 32 NewUser class, 31 newUser.py code listing, 30 usage generator, 32 user registration, 31 JUD (Jabber User Directory), 61-63, 124 registering in Java, 27-28 registering with Python, 31 registering with AIM (AOL Instant Messenger), 149-150 rosters, 136-138 tag, 340
V -v option (jabberd), 40 tag, 282 variables, jid, 417 tag, 42 vCards configuration, 57 synchronizing, 59 verifying OpenSSL installation, 260 version management packets, 338 tag, 338 tag, 338 requesting version information, 338-339 responding to version requests, 339-341 tag, 338 tag, 338
How can we make this index more useful? Email us at
[email protected]
483
484
Visual Basic Jabber-Net sample application
Visual Basic Jabber-Net sample application building, 407 code listing, 404-406
W W3C (World Wide Web Consortium) Web site, 435 Wallace, Dr. Richard, 301 Web services. See services Web Services Description Language (WSDL), 284-285 Web sites Alicebot, 301, 440 Apache server, 350 Cygwin, 35 Exodus, 440 Jabber, Inc., 33 Jabber Software Foundation, 33 Jabber Studio, 61, 123, 439 Jabber-Net, 399 jabberd download site, 35 JabberPy, 414, 440-441 JXTA, 370 Python, 330 Sash, 441 W3C (World Wide Web Consortium), 435 WinJab, 66, 439 XML Cooktop, 441 welcome messages, creating, 58 wild cards, 310 win32all extensions, 330 Windows Event Viewer, 328 Jabber server installation, 39 WinJab configuration, 66-68 SSL (Secure Sockets Layer), 263-265 Web site, 66, 439
World Wide Web Consortium (W3C) Web site, 435 WSDL (Web Services Description Language), 284-285
X tag, 96-98, 140 xa value (presence), 89 xdb (XML database), 124 configuration custom configuration, 45-46 default configuration, 44-45 custom storage, 193-196 global.xdb file, 196 handleXDB() method, 195 initCache() method, 195 XDBBuilder class, 194 xdb tag, 124-126, 132, 137-140 XDBBuilder class, 194 xerces.jar file, 303 XML (Extensible Markup Language), 22, 435 tag, 436-437 tag, 49 tag, 47-48 attributes of, 436 tag, 49 tag, 56, 436 jabber:x:event attachments, 93-94 XML CDATA, 94 tag, 304 tag, 13, 42, 144 tag, 306 comments, 436-437 tag, 57 tag, 47-48 tag, 50-51 tag, 241
XML
tag, 340 tag, 56, 90, 96, 257 tag, 139 tag, 56 tag, 56 tag, 56 tag, 65 tag, 41 tag, 143 tag, 50, 268 tag, 436 tag, 49 tag, 144-145, 188 tag, 262 tag, 50, 262 tag, 20-21, 78, 133, 338, 349 authentication get requests, 234 browse requests, 169-170 client registration, 227 from attribute, 79 id attribute, 79 inventory management service, 208210 namespaces, 82-83 plain authentication, 236-237 reports service registration, 187 search requests, 174-175 simple Telnet example, 74-75 to attribute, 79 type attribute, 79-82 zero-knowledge authentication, 243 tag, 147 Jabber-RPC object lessons, 296 tag, 41 tag, 311 tag, 246 tag, 46-47 tag, 305
, 304 tag, 45 tag, 124 tag, 53 tag, 45 tag, 50 tag, 65 tag, 125, 436 subelement, 93-95 subelement, 96 from attribute, 92 id attribute, 92 simple Telnet example, 76-77 subelement, 95 subelement, 91, 96 to attribute, 92 type attribute, 92-93 subelement, 96-98 tag, 282 tag, 338 namespaces, 12, 20, 437 tag, 65, 349 tag, 65 tag, 14, 56, 142, 149 tag, 56 tag, 338 tag, 282 tag, 282 tag, 306 tag, 84-87, 349 from attribute, 88 id attribute, 88 simple Telnet example, 76 to attribute, 88 type attribute, 88-89 tag, 90 tag, 65 tag, 65
How can we make this index more useful? Email us at
[email protected]
485
486
XML
tag, 65 tag, 50 tag, 308-309 tag, 46 tag, 436 tag, 57 tag, 56 tag, 56 tag, 124, 128-131, 133-137, 247-249 tag, 65 tag, 243 tag, 40, 64, 143 tag, 57 tag, 56, 86, 89 simple document, 435-436 tag, 48-49, 262-263 tag, 86, 89 tag, 78, 130 streams, 438 tag, 56, 95 tag, 311 tag, 306 tag, 311 tag, 308 tag, 91, 96 tag, 45 tag, 243 tag, 306 tag, 56 tag, 340 tag, 56 tag, 42 tag, 340 tag, 282 tag, 42 tag, 338 W3C specification, 435 tag, 96-98, 140 tag, 124-126, 132, 137-140
xdb (XML database), 44-46, 124 XML Cooktop, 441 XML-RPC, 278-283 data types, 282-283 Jabber and, 286 JabberRPCPeerA.py invoker, 287-289 JabberRPCPeerB.py service provider, 289-292 JabberRPCPeerC.py service provider, 292-295 method call XML encoding, 281 method response XML encoding, 282 over-the-wire packaging, 282 reallySimpleXMLRPCInvokee.py service, 280 reallySimpleXMLRPCInvoker.py client, 278-279 transport mechanics, 279 XML-RPC wrapped for Jabber transport, 295 tag, 132 XML Cooktop, 441 XML-RPC, 278 data types, 282-283 Jabber and, 286 Jabber-RPC object lessons, 296 JabberRPCPeerA.py invoker, 287-289 JabberRPCPeerB.py service provider, 289-292 JabberRPCPeerC.py service provider, 292-295 method call XML encoding, 281 method response XML encoding, 282 over-the-wire packaging, 282 reallySimpleXMLRPCInvokee.py service, 280 reallySimpleXMLRPCInvoker.py client, 278-279 transport mechanics, 279 XML-RPC wrapped for Jabber transport, 295 xmlns attribute ( tag), 130
tag
Y-Z -Z option (jabberd), 40 zero-knowledge authentication, 242-245 code listing, 244 packets, 243 mod_auth_Ok module, 242 tag, 132
How can we make this index more useful? Email us at
[email protected]
487