#!/usr/bin/ruby -w # == Synopsis # # This tool will create, configure, and destroy Jabber multi-user chat rooms (XEP-0045). It is designed to work with # Apple's version of mu-conference 0.62. It needs to login to the Jabber server as a client, so valid authentication # credentials are required. A custom XMPP4R library must be installed which works around some bugs in the present XMPP4R trunk. # # == Usage # muc_room_config.rb [options] # Environment: # Authentication credentials (username and password) must be passed in using these environment variables. These are # REQUIRED: # MUC_USER : Username for Jabber login # MUC_PASS : Password for Jabber login # # Options: # -D, --debug: Enable debug mode # -o, --hostname: Jabber server hostname (REQUIRED) # -r, --roomname: Name of Jabber MUC room to create/modify/use (REQUIRED) # # Commands: # Choose one command per execution: # -c, --create: Create and configure a MUC room. # usage: -c # -i, --invite: Sends chat room invitations to 1 or more JIDs. The room must # already be created and configured. # usage: -i ( ...) # -d, --destroy: Destroy a room (not yet implemented) # -h, -?, --help: This usage information # --default: Display the default room configuration and exit # # Additional room configuration options: # Use the following arguments to specify non-default options for room configuration (for use with -c command): # Room Description: # --roomdesc # Leave message (displayed when a user exits the room): # --leave # Join message (displayed when a user joins the room): # --join # Message for user renaming nickname in room: # --rename # Allow Occupants to Change Subject: # --changesubject # Maximum Number of Room Occupants (0 for unlimited): # --maxusers # Allow Occupants to query other Occupants: # --privacy # Allow Public Searching for Room: # --publicroom # Make Room Persistent: # --persistentroom # Consider all Clients as Legacy (shown messages): # --legacy # Make Room Moderated (By default, new users entering a moderated room are only visitors): # --moderatedroom # Make Occupants in a Moderated Room Default to Participant: # --defaulttype # Ban Private Messages between Occupants: # --privmsg # An Invitation is Required to Enter: # --inviteonly # Allow Occupants to Invite Others: # --allowinvites # A Password is required to enter? # --passwordprotected # The Room Password: # --roomsecret # Affiliations that May Discover Real JIDs of Occupants: # --whois # Enable Logging of Room Conversations: # --enablelogging # Logfile format: # --logformat # # # Examples: # env MUC_USER="admin" MUC_PASS="let_me_in" ruby muc_room_config.rb -o chatserver.example.com -r my_room -c --enablelogging true --logformat text --join "Welcome to my_room, enjoy your stay" # env MUC_USER="admin" MUC_PASS="let_me_in" ruby muc_room_config.rb -o chatserver.example.com -r my_room -i bob@chatserver.example.com alice@chatserver.example.com # env MUC_USER="admin" MUC_PASS="let_me_in" ./muc_room_config.rb -o chatserver.example.com -r my_room -d # # == Author # Joel Hedden (jhedden@apple.com) # # == Copyright # Copyright 2007 Apple Inc. All rights reserved. # # == State # Functional, unfinished # # == Todo # * error handling # * list/grant/revoke room ownership # * add logging mechanism # require 'xmpp4r' require 'xmpp4r/muc' #require 'xmpp4r/muc/x' require 'rdoc/usage' include Jabber include MUC ################# # MucRoomTool Class class MucRoomTool # Define constants TOOL_VERSION = "0.1" RESOURCE = 'RubyXMPP4R' DESTROY_REASON = "Your workgroup has been deleted." INVITE_REASON = "You have been automatically invited to this chatroom for an OD workgroup." BOOL = { 'true' => 1, 'false' => 0 } def initialize # Set default room configuration @room_config = { 'FORM_TYPE' => 'http://jabber.org/protocol/muc#roomconfig', 'form' => 'config', 'muc#owner_roomname' => nil, 'muc#owner_roomdesc' => nil, 'leave' => 'has left', 'join' => 'has become available', 'rename' => 'is now known as', 'muc#owner_changesubject' => 0, 'muc#owner_maxusers' => 0, 'privacy' => 0, 'muc#owner_publicroom' => 1, 'muc#owner_persistentroom' => 1, 'legacy' => 0, 'muc#owner_moderatedroom' => 0, 'defaulttype' => 0, 'privmsg' => 0, 'muc#owner_inviteonly' => 0, 'muc#owner_allowinvites' => 0, 'muc#owner_passwordprotectedroom' => 0, 'muc#owner_roomsecret' => nil, 'muc#owner_whois' => 'admins', 'muc#owner_enablelogging' => 0, 'logformat' => 'xml' } # Initialize instance variables @debug = false @username = nil @password = nil @roomname = nil @hostname = nil @client = nil @invite_list = nil @invite = false @create = false @destroy = false @reason = nil @alt_roomname = nil @room_jid = nil @client_jid = nil @client_full_jid = nil @client_muc_jid = nil end def parse_options require 'getoptlong' opts = GetoptLong.new( # Commands [ '--help', '-h', '-?', GetoptLong::NO_ARGUMENT ], [ '--default', GetoptLong::NO_ARGUMENT ], [ '--create', '-c', GetoptLong::NO_ARGUMENT ], [ '--invite', '-i', GetoptLong::NO_ARGUMENT ], [ '--destroy', '-d', GetoptLong::NO_ARGUMENT ], # Options [ '--debug', '-D', GetoptLong::NO_ARGUMENT ], [ '--hostname', '-o', GetoptLong::REQUIRED_ARGUMENT ], [ '--roomname', '-r', GetoptLong::REQUIRED_ARGUMENT ], [ '--reason', '-R', GetoptLong::REQUIRED_ARGUMENT ], # Optional args for room configuration: [ '--roomdesc', GetoptLong::REQUIRED_ARGUMENT ], [ '--leave', GetoptLong::REQUIRED_ARGUMENT ], [ '--join', GetoptLong::REQUIRED_ARGUMENT ], [ '--rename', GetoptLong::REQUIRED_ARGUMENT ], [ '--changesubject', GetoptLong::REQUIRED_ARGUMENT ], [ '--maxusers', GetoptLong::REQUIRED_ARGUMENT ], [ '--privacy', GetoptLong::REQUIRED_ARGUMENT ], [ '--publicroom', GetoptLong::REQUIRED_ARGUMENT ], [ '--persistentroom', GetoptLong::REQUIRED_ARGUMENT ], [ '--legacy', GetoptLong::REQUIRED_ARGUMENT ], [ '--moderatedroom', GetoptLong::REQUIRED_ARGUMENT ], [ '--defaulttype', GetoptLong::REQUIRED_ARGUMENT ], [ '--privmsg', GetoptLong::REQUIRED_ARGUMENT ], [ '--inviteonly', GetoptLong::REQUIRED_ARGUMENT ], [ '--allowinvites', GetoptLong::REQUIRED_ARGUMENT ], [ '--passwordprotectedroom', GetoptLong::REQUIRED_ARGUMENT ], [ '--roomsecret', GetoptLong::REQUIRED_ARGUMENT ], [ '--whois', GetoptLong::REQUIRED_ARGUMENT ], [ '--enablelogging', GetoptLong::REQUIRED_ARGUMENT ], [ '--logformat', GetoptLong::REQUIRED_ARGUMENT ] ) opts.each do |opt, arg| case opt # Commands when '--help' RDoc::usage('usage') when '--default' display_default_room_config exit 0 when '--create' @create = true when '--invite' @invite = true when '--destroy' @destroy = true # Options when '--debug' @debug = true Jabber::debug = true when '--hostname' @hostname = arg when '--roomname' @roomname = arg when '--reason' @reason = arg # Optional args for room configuration: when '--roomdesc' @room_config['muc#owner_roomdesc'] = arg when '--leave' @room_config['leave'] = arg when '--join' @room_config['join'] = arg when '--rename' @room_config['rename'] = arg when '--changesubject' @room_config['muc#owner_changesubject'] = BOOL[arg] when '--maxusers' @room_config['muc#owner_maxusers'] = arg when '--privacy' @room_config['privacy'] = BOOL[arg] when '--publicroom' @room_config['muc#owner_publicroom'] = BOOL[arg] when '--persistentroom' @room_config['muc#owner_persistentroom'] = BOOL[arg] when '--legacy' @room_config['legacy'] = BOOL[arg] when '--moderatedroom' @room_config['muc#owner_moderatedroom'] = BOOL[arg] when '--defaulttype' @room_config['defaulttype'] = BOOL[arg] when '--privmsg' @room_config['privmsg'] = BOOL[arg] when '--inviteonly' @room_config['muc#owner_inviteonly'] = BOOL[arg] when '--allowinvites' @room_config['muc#owner_allowinvites'] = BOOL[arg] when '--passwordprotectedroom' @room_config['muc#owner_passwordprotectedroom'] = BOOL[arg] when '--roomsecret' @room_config['muc#owner_roomsecret'] = arg when '--whois' @room_config['muc#owner_whois'] = arg when '--enablelogging' @room_config['muc#owner_enablelogging'] = BOOL[arg] when '--logformat' @room_config['logformat'] = arg end end # JJJ debugging: @room_config.each {|key, value| puts "#{key} is #{value}"} # if --invite was specified, load the remaining args into a JID array if (@invite) @invite_list = ARGV end # Get authentication args from environment @username = ENV['MUC_USER'] @password = ENV['MUC_PASS'] # Configure variables that are based on user input if (@username && @password && @hostname && RESOURCE && @roomname) @room_jid = "#{@roomname}@conference.#{@hostname}" @client_jid = "#{@username}@#{@hostname}" @client_full_jid = "#{@client_jid}/#{RESOURCE}" @client_muc_jid = "#{@room_jid}/#{@username}" @room_config['muc#owner_roomname'] = @roomname if (! @room_config['muc#owner_roomdesc']) @room_config['muc#owner_roomdesc'] = @roomname end @alt_roomname = "#{@roomname}_alt@conference.#{@hostname}" else puts "Some required arguments were not provided. See usage:\n" RDoc::usage_no_exit('usage') exit 1 end end # This is rewritten from MUC::MUCClient::Configure to work around a couple of bugs: # 1. original code calls stream.send_with_id(iq) which returns NIL # 2. original code sends the wrong JID when requesting a config form, so it failed def configure_room(client, muc, room_jid, options={}) raise 'You are not the owner' unless muc.owner? iq = Iq.new(:get, room_jid) iq.to = room_jid iq.from = muc.my_jid iq.add(IqQueryMUCOwner.new) fields = [] #answer = client.send_with_id(iq) #raise "Configuration not possible for this room" unless answer.query && answer.query.x(XData) #answer.query.x(XData).fields.each { |field| # if (var = field.attributes['var']) # fields << var # end #} #JJJ send_with_id was returning NIL (when an answer was available) so the above failed. # The following may not raise an exception if we get no answer. client.send_with_id(iq) # fill out the reply form iq = Iq.new(:set, room_jid) iq.to = room_jid iq.from = muc.my_jid query = IqQueryMUCOwner.new form = Dataforms::XData.new form.type = :submit options.each do |var, values| field = Dataforms::XDataField.new values = [values] unless values.is_a?(Array) field.var, field.values = var, values form.add(field) end query.add(form) iq.add(query) client.send_with_id(iq) return true end def destroy_room(client, client_full_jid, room_jid, reason, alt_room_jid) iq = Iq.new(:set, room_jid) iq.to = room_jid iq.from = client_full_jid query = IqQueryMUCOwner.new elem_destroy = REXML::Element::new('destroy') if (alt_room_jid) elem_destroy.attributes['jid'] = alt_room_jid end if (reason) elem_reason = REXML::Element::new('reason').add_text(reason) elem_destroy.add_element(elem_reason) end query.add(elem_destroy) iq.add(query) client.send_with_id(iq) return true end def join_room(client, client_muc_jid, client_full_jid) muc = MUCClient.new(client) muc.my_jid = client_full_jid muc.join(client_muc_jid) muc end def connect_client(client_full_jid, password) client = Client.new(client_full_jid) client.connect client.auth_nonsasl(password, false) client.send(Presence::new) client end def send_invites(client, client_jid, room_jid, reason, recipients) recipients.each { |jid| msg = Message.new msg.from = client_jid msg.to = room_jid x = msg.add(XMUCUser.new) x.add(XMUCUserInvite.new(jid, reason)) client.send(msg) } return true end def display_default_room_config puts "Default room configuration (room name and description are configured by the -r argument):\n" @room_config.each {|key, value| puts "#{key} = #{value}"} end #### Unfinished def room_exists?() end # def get_room_owners() end # def grant_room_ownership() end # def revoke_room_ownership() end #### ######################################################################## # Execute: Perform operations based on user input options def execute client = connect_client(@client_full_jid, @password) ### Perform the user-requested operation(s) # Create a room if (@create && @roomname) puts "Creating room #{@roomname}...\n" #if room_exists?(@roomname) muc = join_room(client, @client_muc_jid, @client_full_jid) configure_room(client, muc, @room_jid, @room_config) puts "Done creating and configuring room #{@roomname}.\n" end # Invite users to a room (room must exist!) if (@invite && @roomname && @invite_list) puts "Joining room #{@roomname}...\n" muc = join_room(client, @client_muc_jid, @client_full_jid) puts "Sending chat room invitations...\n" send_invites(client, @client_full_jid, @room_jid, (@reason != nil) ? @reason : INVITE_REASON, @invite_list) puts "Done sending chat room invitations.\n" end # Destroy a room if (@destroy && @roomname) puts "Destroying room #{@roomname}...\n" join_room(client, @client_muc_jid, @client_full_jid) destroy_room(client, @client_full_jid, @room_jid, (@reason != nil) ? @reason : DESTROY_REASON, @alt_roomname) puts "Done destroying room #{@roomname}...\n" end return true end end # class MucRoomTool # JJJ: get completed room config form (create a method...) #iq = Iq.new(:get, $room_jid) #iq.from = $client_full_jid #query = IqQueryMUCOwner.new #iq.add(query) #$client.send_with_id(iq) ################# # MAIN muc_room_tool = MucRoomTool.new muc_room_tool.parse_options status = muc_room_tool.execute ? 0 : 1 exit status