def __init__(self, jid, password, configuration, name, auto_register=True, auto_reconnect=True): """ The constructor of the class. @type jid: string @param jid: the jid of the client. @type password: string @param password: the password of the JID account. @type configuration: Config object @param configuration: the configuration object @type name: string @param name: the name of the entity @type auto_register: Boolean @param auto_register: if rTue, entity will try to create a XMPP account if needed @type auto_reconnect: Boolean @param auto_reconnect: if True, entity will try to reconnect if disconnected """ self.name = name self.xmppstatus = None self.xmppstatusshow = None self.xmppclient = None self.vCard = None self.password = password self.jid = jid self.resource = self.jid.getResource() self.roster = None self.roster_retreived = False self.configuration = configuration self.auto_register = auto_register self.auto_reconnect = auto_reconnect self.messages_registrar = [] self.isAuth = False self.loop_status = ARCHIPEL_XMPP_LOOP_OFF self.pubsubserver = self.configuration.get("GLOBAL", "xmpp_pubsub_server") self.log = TNArchipelLogger(self) self.pubSubNodeEvent = None self.pubSubNodeLog = None self.entity_type = "not-defined" self.permission_center = None self.plugins = []; if isinstance(self, TNHookableEntity): TNHookableEntity.__init__(self, self.log) if isinstance(self, TNAvatarControllableEntity): TNAvatarControllableEntity.__init__(self, configuration, self.permission_center, self.xmppclient, self.log) if isinstance(self, TNTaggableEntity): TNTaggableEntity.__init__(self, self.pubsubserver, self.jid, self.xmppclient, self.permission_center, self.log) if isinstance(self, TNRosterQueryableEntity): TNRosterQueryableEntity.__init__(self, configuration, self.permission_center, self.xmppclient, self.log) if self.name == "auto": self.name = self.resource if isinstance(self, TNHookableEntity): self.create_hook("HOOK_ARCHIPELENTITY_XMPP_CONNECTED") self.create_hook("HOOK_ARCHIPELENTITY_XMPP_DISCONNECTED") self.create_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED") self.create_hook("HOOK_ARCHIPELENTITY_XMPP_LOOP_STARTED") self.create_hook("HOOK_ARCHIPELENTITY_XMPP_LOOP_STOPPED") ## recover/create pubsub after connection self.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", self.recover_pubsubs) self.log.info("jid defined as %s" % (str(self.jid))) ip_conf = self.configuration.get("GLOBAL", "machine_ip") if ip_conf == "auto": self.ipaddr = socket.gethostbyname(socket.gethostname()) else: self.ipaddr = ip_conf
class TNArchipelEntity (object): """ this class represent a basic XMPP Client """ def __init__(self, jid, password, configuration, name, auto_register=True, auto_reconnect=True): """ The constructor of the class. @type jid: string @param jid: the jid of the client. @type password: string @param password: the password of the JID account. @type configuration: Config object @param configuration: the configuration object @type name: string @param name: the name of the entity @type auto_register: Boolean @param auto_register: if rTue, entity will try to create a XMPP account if needed @type auto_reconnect: Boolean @param auto_reconnect: if True, entity will try to reconnect if disconnected """ self.name = name self.xmppstatus = None self.xmppstatusshow = None self.xmppclient = None self.vCard = None self.password = password self.jid = jid self.resource = self.jid.getResource() self.roster = None self.roster_retreived = False self.configuration = configuration self.auto_register = auto_register self.auto_reconnect = auto_reconnect self.messages_registrar = [] self.isAuth = False self.loop_status = ARCHIPEL_XMPP_LOOP_OFF self.pubsubserver = self.configuration.get("GLOBAL", "xmpp_pubsub_server") self.log = TNArchipelLogger(self) self.pubSubNodeEvent = None self.pubSubNodeLog = None self.entity_type = "not-defined" self.permission_center = None self.plugins = []; if isinstance(self, TNHookableEntity): TNHookableEntity.__init__(self, self.log) if isinstance(self, TNAvatarControllableEntity): TNAvatarControllableEntity.__init__(self, configuration, self.permission_center, self.xmppclient, self.log) if isinstance(self, TNTaggableEntity): TNTaggableEntity.__init__(self, self.pubsubserver, self.jid, self.xmppclient, self.permission_center, self.log) if isinstance(self, TNRosterQueryableEntity): TNRosterQueryableEntity.__init__(self, configuration, self.permission_center, self.xmppclient, self.log) if self.name == "auto": self.name = self.resource if isinstance(self, TNHookableEntity): self.create_hook("HOOK_ARCHIPELENTITY_XMPP_CONNECTED") self.create_hook("HOOK_ARCHIPELENTITY_XMPP_DISCONNECTED") self.create_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED") self.create_hook("HOOK_ARCHIPELENTITY_XMPP_LOOP_STARTED") self.create_hook("HOOK_ARCHIPELENTITY_XMPP_LOOP_STOPPED") ## recover/create pubsub after connection self.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", self.recover_pubsubs) self.log.info("jid defined as %s" % (str(self.jid))) ip_conf = self.configuration.get("GLOBAL", "machine_ip") if ip_conf == "auto": self.ipaddr = socket.gethostbyname(socket.gethostname()) else: self.ipaddr = ip_conf def initialize_modules(self, group): """ this will initializes all plugins @type group: string @param group: the name of the entry point group to load """ excluded_plugins = [] loading_module_policy = self.configuration.get("GLOBAL", "module_loading_policy") if not loading_module_policy in ('restrictive', 'permissive'): self.log.error("PLUGIN: loading_module_policy in configuration must be set to 'restrictive' or 'permissive'. Consider this as a major failure.") return for factory_method in iter_entry_points(group=group, name="factory"): method = factory_method.load() plugins = method(self.configuration, self, group) for plugin in plugins: plugin_info = plugin["info"] if loading_module_policy == "restrictive": if not self.configuration.has_option("MODULES", plugin_info["identifier"]): self.log.info("PLUGIN: plugin %s has not been loaded as it is not desfined in configuration and loading mode is restrictive" % plugin_info["identifier"]) continue elif loading_module_policy == "permissive": if self.configuration.has_option("MODULES", plugin_info["identifier"]): if not self.configuration.getboolean("MODULES", plugin_info["identifier"]): self.log.info("PLUGIN: plugin %s has not been loaded as it is excluded by configuration" % plugin_info["identifier"]) continue if plugin_info["configuration-section"]: if not self.configuration.has_section(plugin_info["configuration-section"]): excluded_plugins.append(plugin_info["identifier"]) self.log.error("PLUGIN: plugin %s needs configuration section with name [%s]" % (plugin_info["identifier"], plugin_info["configuration-section"])) sys.exit(-1) for needed_token in plugin_info["configuration-tokens"]: if not self.configuration.has_option(plugin_info["configuration-section"], needed_token): excluded_plugins.append(plugin_info["identifier"]) self.log.error("PLUGIN: plugin %s needs configuration option with name %s" % (plugin_info["identifier"], needed_token)) sys.exit(-1) self.log.info("PLUGIN: loaded plugin %s " % plugin_info["identifier"]) self.plugins.append(plugin) def get_plugin(self, identifier): """ return the plugin object with given identifier @type identifier: string @param identifier: the identifier of the plugin @rtype: object @return: the requested plugin or None """ for plugin in self.plugins: if plugin["info"]["identifier"] == identifier: return plugin["plugin"] return None def check_acp(self, conn, iq): """ check is iq is a valid ACP and return action. it it's not valid, the will terminate the stanza processing and will return to the origin client a standard Archipel error IQ @type conn: xmpp connection @param conn: the current current XMPP connection @type iq: xmpp.Iq @param iq: the iq to check @rtype: string or none @return: if the ACP is valid, it will return the requested action. otherwise it'll send ARCHIPEL_NS_ERROR_QUERY_NOT_WELL_FORMED iq to the sender and raise xmpp.protocol.NodeProcessed """ try: action = iq.getTag("query").getTag("archipel").getAttr("action") self.log.info("acp received: from: %s, type: %s, namespace: %s, action: %s" % (iq.getFrom(), iq.getType(), iq.getQueryNS(), action)) return action except Exception as ex: reply = build_error_iq(self, ex, iq, ARCHIPEL_NS_ERROR_QUERY_NOT_WELL_FORMED) conn.send(reply) raise xmpp.protocol.NodeProcessed ### vocabulary def init_vocabulary(self): """ initialize the vocabulary """ if isinstance(self, TNRosterQueryableEntity): TNRosterQueryableEntity.init_vocabulary(self) ### Permissions def init_permissions(self): """ Initializes the permissions overrides this to add custom permissions """ self.log.info("initializing permissions of %s" % self.jid) if isinstance(self, TNTaggableEntity): TNTaggableEntity.init_permissions(self) if isinstance(self, TNAvatarControllableEntity): TNAvatarControllableEntity.init_permissions(self) self.permission_center.create_permission("all", "All permissions are granted", False) self.permission_center.create_permission("presence", "Authorizes users to request presences", False) self.permission_center.create_permission("message", "Authorizes users to send messages", False) self.permission_center.create_permission("permission_get", "Authorizes users to get all permissions", True) self.permission_center.create_permission("permission_getown", "Authorizes users to get only own permissions", False) self.permission_center.create_permission("permission_list", "Authorizes users to list existing", False) self.permission_center.create_permission("permission_set", "Authorizes users to set all permissions", False) self.permission_center.create_permission("permission_setown", "Authorizes users to set only own permissions", False) self.permission_center.create_permission("subscription_add", "Authorizes users add others in entity roster", False) self.permission_center.create_permission("subscription_remove", "Authorizes users remove others in entity roster", False) self.log.info("permissions of %s initialized" % self.jid) def check_perm(self, conn, stanza, action_name, error_code=-1, prefix=""): """ check if given from of stanza has a given permission @type conn: xmpp.Dispatcher @param conn: ths instance of the current connection that send the message @type stanza: xmpp.Node @param stanza: the original stanza @type action_name: string @param action_name: the name of action to check permission @type error_code: int @param error_code: the return code of permission denied @type prefix: string @param prefix: the prefix of action_name (for example if permission if health_get and action is get, you can give 'health_' as prefix) """ self.log.info("checking permission for action %s%s asked by %s" % (prefix, action_name, stanza.getFrom())) if not self.permission_center.check_permission(str(stanza.getFrom().getStripped()), "%s%s" % (prefix, action_name)): conn.send(build_error_iq(self, "Cannot use '%s': permission denied" % action_name, stanza, code=error_code, ns=ARCHIPEL_NS_PERMISSION_ERROR)) raise xmpp.protocol.NodeProcessed ### Server connection def connect_xmpp(self): """ Initialize the connection to the the XMPP server exit on any error. @rtype: Boolean @return: True in case of success """ self.xmppclient = xmpp.Client(self.jid.getDomain(), debug=[]) #debug=['dispatcher', 'nodebuilder', 'protocol']) if self.xmppclient.connect() == "": self.log.error("unable to connect to XMPP server") if self.auto_reconnect: self.loop_status = ARCHIPEL_XMPP_LOOP_RESTART return False else: sys.exit(-1) self.loop_status = ARCHIPEL_XMPP_LOOP_ON self.log.info("sucessfully connected") self.perform_hooks("HOOK_ARCHIPELENTITY_XMPP_CONNECTED") return True def auth_xmpp(self): """ Authentify the client to the XMPP server """ self.log.info("trying to authentify the client") if self.xmppclient.auth(self.jid.getNode(), self.password, self.resource) == None: self.isAuth = False if (self.auto_register): self.log.info("starting registration, according to propertie auto_register") self.inband_registration() return self.log.error("bad authentication. exiting") sys.exit(0) self.log.info("sucessfully authenticated") self.isAuth = True self.loop_status = ARCHIPEL_XMPP_LOOP_ON self.register_handler() self.roster = self.xmppclient.getRoster() self.perform_hooks("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED") def connect(self): """ Connect and auth to XMPP Server """ if self.xmppclient and self.xmppclient.isConnected(): self.log.warning("trying to connect, but already connected. ignoring") return if self.connect_xmpp(): self.auth_xmpp() def disconnect(self): """ Close the connections from XMPP server """ if self.xmppclient and self.xmppclient.isConnected(): self.isAuth = False self.loop_status = ARCHIPEL_XMPP_LOOP_OFF self.perform_hooks("HOOK_ARCHIPELENTITY_XMPP_DISCONNECTED") else: self.log.warning("trying to disconnect, but not connected. ignoring") ### Pubsub def recover_pubsubs(self, origin, user_info, arguments): """ create or get the current hypervisor pubsub node. arguments here are used to be HOOK compliant see @register_hook """ TNTaggableEntity.recover_pubsubs(self, origin, user_info, arguments) # creating/getting the event pubsub node eventNodeName = "/archipel/" + self.jid.getStripped() + "/events" self.pubSubNodeEvent = archipelcore.pubsub.TNPubSubNode(self.xmppclient, self.pubsubserver, eventNodeName) if not self.pubSubNodeEvent.recover(wait=True): self.pubSubNodeEvent.create(wait=True) self.pubSubNodeEvent.configure({ archipelcore.pubsub.XMPP_PUBSUB_VAR_ACCESS_MODEL: archipelcore.pubsub.XMPP_PUBSUB_VAR_ACCESS_MODEL_OPEN, archipelcore.pubsub.XMPP_PUBSUB_VAR_DELIVER_NOTIFICATION: 1, archipelcore.pubsub.XMPP_PUBSUB_VAR_PERSIST_ITEMS: 0, archipelcore.pubsub.XMPP_PUBSUB_VAR_NOTIFY_RECTRACT: 0, archipelcore.pubsub.XMPP_PUBSUB_VAR_DELIVER_PAYLOADS: 1, archipelcore.pubsub.XMPP_PUBSUB_VAR_SEND_LAST_PUBLISHED_ITEM: archipelcore.pubsub.XMPP_PUBSUB_VAR_SEND_LAST_PUBLISHED_ITEM_NEVER }, wait=True) # creating/getting the log pubsub node logNodeName = "/archipel/" + self.jid.getStripped() + "/logs" self.pubSubNodeLog = archipelcore.pubsub.TNPubSubNode(self.xmppclient, self.pubsubserver, logNodeName) if not self.pubSubNodeLog.recover(wait=True): self.pubSubNodeLog.create(wait=True) self.pubSubNodeLog.configure({ archipelcore.pubsub.XMPP_PUBSUB_VAR_ACCESS_MODEL: archipelcore.pubsub.XMPP_PUBSUB_VAR_ACCESS_MODEL_OPEN, archipelcore.pubsub.XMPP_PUBSUB_VAR_DELIVER_NOTIFICATION: 1, archipelcore.pubsub.XMPP_PUBSUB_VAR_MAX_ITEMS: self.configuration.get("LOGGING", "log_pubsub_max_items"), archipelcore.pubsub.XMPP_PUBSUB_VAR_PERSIST_ITEMS: 1, archipelcore.pubsub.XMPP_PUBSUB_VAR_NOTIFY_RECTRACT: 0, archipelcore.pubsub.XMPP_PUBSUB_VAR_DELIVER_PAYLOADS: 1, archipelcore.pubsub.XMPP_PUBSUB_VAR_SEND_LAST_PUBLISHED_ITEM: archipelcore.pubsub.XMPP_PUBSUB_VAR_SEND_LAST_PUBLISHED_ITEM_NEVER }, wait=True) def remove_pubsubs(self): """ delete own entity pubsubs """ self.log.info("removing pubsub node for log") self.pubSubNodeLog.delete(wait=True) self.log.info("removing pubsub node for events") self.pubSubNodeEvent.delete(wait=True) ### Basic handlers def register_handler(self): """ this method have to be overloaded in order to register handler for XMPP events """ if isinstance(self, TNTaggableEntity): TNTaggableEntity.register_handler(self) if isinstance(self, TNAvatarControllableEntity): TNAvatarControllableEntity.register_handler(self) self.xmppclient.RegisterHandler('presence', self.process_presence) self.xmppclient.RegisterHandler('message', self.process_message, typ="chat") self.xmppclient.RegisterHandler('iq', self.process_permission_iq, ns=ARCHIPEL_NS_PERMISSIONS) self.xmppclient.RegisterHandler('iq', self.process_subscription_iq, ns=ARCHIPEL_NS_SUBSCRIPTION) for plugin in self.plugins: self.log.info("PLUGIN: registering stanza handler for plugin %s" % plugin["info"]["identifier"]) plugin["plugin"].register_for_stanza() self.log.info("handlers registred") ### Presence Management def process_presence(self, conn, presence): """ process presence stanzas @type conn: xmpp.Dispatcher @param conn: ths instance of the current connection that send the message @type presence: xmpp.Protocol.Iq @param presence: the received IQ """ if presence.getFrom().getStripped() == self.jid.getStripped(): raise xmpp.protocol.NodeProcessed if not presence.getType() in ("subscribe", "unsubscribe"): raise xmpp.protocol.NodeProcessed self.log.info("presence stanza received from %s: %s" % (presence.getFrom(), presence.getType())) # update roster is necessary if not self.roster: self.roster = self.xmppclient.getRoster() typ = presence.getType() jid = presence.getFrom() self.log.info("managing subscribtion request with type %s" % presence.getType()) # check permissions if not self.permission_center.check_permission(jid.getStripped(), "presence"): if typ == "subscribe": self.unsubscribe(jid) self.remove_jid(jid) raise xmpp.protocol.NodeProcessed # if everything is all right, process request if typ == "subscribe": self.authorize(jid) elif typ == "unsubscribe": self.remove_jid(jid) raise xmpp.protocol.NodeProcessed ### Subscription Management def process_subscription_iq(self, conn, iq): """ process presence iq with namespace ARCHIPEL_NS_SUBSCRIPTION. this allows to ask entity to subscribe to others users it understands: - add - remove @type conn: xmpp.Dispatcher @param conn: ths instance of the current connection that send the message @type presence: xmpp.Protocol.Iq @param presence: the received IQ """ reply = None action = self.check_acp(conn, iq) self.check_perm(conn, iq, action, -1, prefix="subscription_") if action == "add": reply = self.iq_add_subscription(iq) elif action == "remove": reply = self.iq_remove_subscription(iq) if reply: conn.send(reply) raise xmpp.protocol.NodeProcessed def iq_add_subscription(self, iq): """ add a JID in the entity roster @type iq: xmpp.Protocol.Iq @param iq: the IQ containing the request """ try: reply = iq.buildReply("result") jid = xmpp.JID(iq.getTag("query").getTag("archipel").getAttr("jid")) self.log.info("add jid %s into %s's roster" % (str(jid), str(self.jid))) self.permission_center.grant_permission_to_user("presence", jid.getStripped()) self.push_change("permissions", "set") self.add_jid(jid) self.authorize(jid) except Exception as ex: reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_ADD_SUBSCRIPTION) return reply def iq_remove_subscription(self, iq): """ remove a JID from the entity roster @type iq: xmpp.Protocol.Iq @param iq: the IQ containing the request """ try: reply = iq.buildReply("result") jid = xmpp.JID(iq.getTag("query").getTag("archipel").getAttr("jid")) self.permission_center.revoke_permission_to_user("presence", jid.getStripped()) self.push_change("permissions", "set") self.remove_jid(jid) except Exception as ex: reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_REMOVE_SUBSCRIPTION) return reply ### XMPP Utilities def change_presence(self, presence_show=None, presence_status=None): """ change the presence of the entity @type presence_show: string @param presence_show: the value of the XMPP show @type presence_status: string @param presence_status: the value of the XMPP status """ self.xmppstatus = presence_status self.xmppstatusshow = presence_show self.log.info("status change: %s show:%s" % (self.xmppstatus, self.xmppstatusshow)) pres = xmpp.Presence(status=self.xmppstatus, show=self.xmppstatusshow) self.xmppclient.send(pres) def change_status(self, presence_status): """ change only the status of the entity @type presence_status: string @param presence_status: the value of the XMPP status """ self.xmppstatus = presence_status pres = xmpp.Presence(status=self.xmppstatus, show=self.xmppstatusshow) self.xmppclient.send(pres) def push_change(self, namespace, change): """ push a change using archipel push system. this system will change with inclusion of pubsub @type namespace: string @param namespace: the namespace of the push. it will be prefixed with @ARCHIPEL_NS_IQ_PUSH @type change: string @param change: the change value (can be anything, like 'newvm' in the context of the namespace) """ ns = ARCHIPEL_NS_IQ_PUSH + ":" + namespace self.log.info("PUSH : pushing %s->%s" % (ns, change)) push = xmpp.Node(tag="push", attrs={"date": datetime.datetime.now(), "xmlns": ns, "change": change}) self.pubSubNodeEvent.add_item(push) def shout(self, subject, message): """ send a message to everybody in roster @type subject: string @param subject: the xmpp subject of the message @type message: string @param message: the content of the message """ for barejid in self.roster.getItems(): if self.jid.getStripped() == barejid: continue resources = self.roster.getResources(barejid) if len(resources) == 0: broadcast = xmpp.Message(body=message, typ="headline", to=barejid) self.log.info("SHOUTING : shouting message to %s" % (barejid)) self.xmppclient.send(broadcast) else: for resource in resources: broadcast = xmpp.Message(body=message, typ="headline", to=barejid + "/" + resource) self.log.info("SHOUTING : shouting message to %s" % (barejid)) self.xmppclient.send(broadcast) ### XMPP Roster def add_jid(self, jid, groups=[]): """ Add a jid to the VM Roster and authorizes it @type jid: xmpp.JID @param jid: this jid to add """ self.log.info("adding JID %s to roster of %s" % (str(jid), str(self.jid))) if not self.roster: self.roster = self.xmppclient.getRoster() self.roster.setItem(jid=jid.getStripped(), groups=groups) self.subscribe(jid) self.push_change("subscription", "added") def remove_jid(self, jid): """ Remove a jid from roster and unauthorizes it @type jid: xmpp.JID @param jid: this jid to remove """ self.log.info("%s is removing jid %s from it's roster" % (str(self.jid), str(jid))) if not self.roster: self.roster = self.xmppclient.getRoster() self.roster.delItem(jid.getStripped()) self.push_change("subscription", "removed") def subscribe(self, jid): """ perform a subscription. we do not user the xmpp.roster.Subscribe() because it doesn't support the name @type jid: xmpp.JID @param jid: this jid to remove """ self.log.info("%s is subscribing to jid %s" % (str(self.jid), str(jid))) presence = xmpp.Presence(to=jid, typ='subscribe') if self.name: presence.addChild(name="nick", namespace="http://jabber.org/protocol/nick", payload=self.name) self.xmppclient.send(presence) def unsubscribe(self, jid): """ perform a unsubscription. @type jid: xmpp.JID @param jid: this jid to remove """ self.log.info("%s is unsubscribing from jid %s" % (str(self.jid), str(jid))) if not self.roster: self.roster = self.xmppclient.getRoster() self.roster.Unsubscribe(jid.getStripped()) self.roster.Unauthorize(jid.getStripped()) def authorize(self, jid): """ authorize the given JID @type jid: xmpp.JID @param jid: this jid to remove """ self.log.info("%s is authorizing jid %s" % (str(self.jid), str(jid))) if not self.roster: self.roster = self.xmppclient.getRoster() self.roster.Authorize(jid) def unauthorize(self, jid): """ unauthorize the given JID @type jid: xmpp.JID @param jid: this jid to remove """ self.log.info("%s is authorizing jid %s" % (str(self.jid), str(jid))) if not self.roster: self.roster = self.xmppclient.getRoster() self.roster.Unauthorize(jid) def is_subscribed(self, jid): """ Check if the JID is authorized or not @type jid: string @param jid: the jid to check in policy @rtype : boolean @return: False if not subscribed or True if subscribed """ try: subs = self.roster.getSubscription(str(jid)) self.log.info("stanza sent form authorized JID %s" % jid) if subs in ("both", "to"): return True return False except KeyError: self.log.info("stanza sent form unauthorized JID %s" % jid) return False ### VCARD management def manage_vcard(self): """ retrieve vCard from server """ self.log.info("asking for own vCard") node_iq = xmpp.Iq(typ='get') node_iq.addChild(name="vCard", namespace="vcard-temp") self.xmppclient.SendAndCallForResponse(stanza=node_iq, func=self.did_receive_vcard) def did_receive_vcard(self, conn, vcard): """ callback of manage_vcard() """ self.vCard = vcard.getTag("vCard") if self.vCard and self.vCard.getTag("PHOTO"): self.b64Avatar = self.vCard.getTag("PHOTO").getTag("BINVAL").getCDATA() self.log.info("own vcard retrieved") self.set_vcard() def set_vcard(self, params={}): """ allows to define a vCard type for the entry set the vcard of the entity @type params: dict @param params: the parameters of the vCard """ try: self.log.info("vcard making started") node_iq = xmpp.Iq(typ='set', xmlns=None) type_node = xmpp.Node(tag="ROLE") payload = [] type_node.setData(self.entity_type) payload.append(type_node) if self.name: name_node = xmpp.Node(tag="FN") name_node.setData(self.name) payload.append(name_node) if self.configuration.getboolean("GLOBAL", "use_avatar"): if not self.b64Avatar: if params and params["filename"]: self.b64avatar_from_filename(params["filename"]) else: self.b64avatar_from_filename(self.default_avatar) node_photo_content_type = xmpp.Node(tag="TYPE") node_photo_content_type.setData("image/png") node_photo_data = xmpp.Node(tag="BINVAL") node_photo_data.setData(self.b64Avatar) node_photo = xmpp.Node(tag="PHOTO", payload=[node_photo_content_type, node_photo_data]) payload.append(node_photo) node_iq.addChild(name="vCard", payload=payload, namespace="vcard-temp") self.xmppclient.SendAndCallForResponse(stanza=node_iq, func=self.send_update_vcard) self.log.info("vcard information sent with type: %s" % self.entity_type) except Exception as ex: self.log.error("error during setting vcard (set_vcard) using stanza: %s EXCEPTION IS: %s" % (str(node_iq), str(ex))) def send_update_vcard(self, conn, presence, photo_hash=None): """ this method is called by set_vcard_entity_type when the update of the vCard is OK. It will send the presence stanza to indicates the update of the vCard @type conn: xmpp.Dispatcher @param conn: ths instance of the current connection that send the message @type presence: xmpp.Protocol.Iq @param presence: the received IQ @type photo_hash: string @param photo_hash: the SHA-1 hash of the photo that changes (optionnal) """ node_presence = xmpp.Presence(status=self.xmppstatus, show=self.xmppstatusshow) if photo_hash: node_photo_sha1 = xmpp.Node(tag="photo") node_photo_sha1.setData(photo_hash) node_presence.addChild(name="x", namespace='vcard-temp:x:update') self.xmppclient.send(node_presence) self.log.info("vcard update presence sent") ### Inband registration management def inband_registration(self): """ Do a in-band registration if auth fail """ if not self.auto_register: return self.log.info("trying to register with %s to %s" % (self.jid.getNode(), self.jid.getDomain())) iq = (xmpp.Iq(typ='set', to=self.jid.getDomain())) payload_username = xmpp.Node(tag="username") payload_username.addData(self.jid.getNode()) payload_password = xmpp.Node(tag="password") payload_password.addData(self.password) iq.setQueryNS("jabber:iq:register") iq.setQueryPayload([payload_username, payload_password]) self.log.info("registration information sent. wait for response") resp_iq = self.xmppclient.SendAndWaitForResponse(iq) if resp_iq.getType() == "error": self.log.error("unable to register : %s" % str(resp_iq)) sys.exit(-1) elif resp_iq.getType() == "result": self.log.info("the registration complete") self.loop_status = ARCHIPEL_XMPP_LOOP_RESTART def inband_unregistration(self): """ Do a in-band unregistration """ self.loop_status = ARCHIPEL_XMPP_LOOP_REMOVE_USER def process_inband_unregistration(self): """ perform the inband unregistration. The account will be removed from the server, and so, the loop will be interrupted """ self.remove_pubsubs() self.log.info("trying to unregister") iq = (xmpp.Iq(typ='set', to=self.jid.getDomain())) iq.setQueryNS("jabber:iq:register") remove_node = xmpp.Node(tag="remove") iq.setQueryPayload([remove_node]) self.log.info("unregistration information sent. waiting for response") resp_iq = self.xmppclient.SendAndWaitForResponse(iq) if resp_iq.getType() == "result": self.log.info("account removed") else: self.log.error("unable to remove account: %s" % str(resp_iq)) self.loop_status = ARCHIPEL_XMPP_LOOP_OFF ### XMPP Message registrars def process_message(self, conn, msg): """ Handler for incoming message. @type conn: xmpp.Dispatcher @param conn: ths instance of the current connection that send the message @type msg: xmpp.Protocol.Message @param msg: the received message """ try: self.log.info("chat message received from %s to %s: %s" % (msg.getFrom(), str(self.jid), msg.getBody())) reply_stanza = self.filter_message(msg) reply = None if reply_stanza: if self.permission_center.check_permission(str(msg.getFrom().getStripped()), "message"): reply = self.build_reply(reply_stanza, msg) else: reply = msg.buildReply("I'm sorry, my parents aren't allowing me to talk to strangers") except Exception as ex: reply = msg.buildReply("Cannot process the message: error is %s" % str(ex)) if reply: conn.send(reply) def add_message_registrar_item(self, item): """ Register a method described in item the item use the following form: { "commands" : ["command trigger 1", "command trigger 2"], "parameters": [ {"name": "param1", "description": "the description of the first param"}, {"name": "param2", "description": "the description of the second param"} ], "method": self.a_method_to_launch, "permissions": "the permissions in a array you need to process the command", "description": "A general description of the command" } The "method" key take any method with type (string)aMethod(raw_command_message). The return string will be sent to the requester @type item: dictionnary @param item: the dictionnary describing the registrar item """ self.log.debug("module have registred a method %s for commands %s" % (str(item["method"]), str(item["commands"]))) self.messages_registrar.append(item) def add_message_registrar_items(self, items): """ register an array of item see @add_message_registrar_item @type item: array @param item: an array of messages_registrar items """ for item in items: self.add_message_registrar_item(item) def filter_message(self, msg): """ this method filter archipel push messages and archipel service messages @type conn: xmpp.Dispatcher @param conn: ths instance of the current connection that send the message @type msg: xmpp.Protocol.Message @param msg: the received message """ if not msg.getType() == ARCHIPEL_NS_SERVICE_MESSAGE and not msg.getType() == ARCHIPEL_NS_IQ_PUSH and not msg.getType() == "error" and msg.getBody(): self.log.info("message received from %s (%s)" % (msg.getFrom(), msg.getType())) reply = msg.buildReply("not prepared") me = reply.getFrom() me.setResource(self.resource) reply.setType("chat") return reply else: self.log.info("message ignored from %s (%s)" % (msg.getFrom(), msg.getType())) return False def build_reply(self, reply_stanza, msg): """ parse the registrar and execute commands if necessary @type reply_stanza: xmpp.Node @param reply_stanza: the stanza to use to reply @type msg: xmpp.Protocol.Message @param msg: the received message """ body = "%s" % msg.getBody().lower() reply_stanza.setBody("I'm sorry, I've not understood what you mean. You can type 'help' to get all command I understand") if body.find("help", 0, len("help")) >= 0: reply_stanza.setBody(self.build_help(msg)) else: loop = True for registrar_item in self.messages_registrar: for cmd in registrar_item["commands"]: if body.find(cmd, 0, len(cmd)) >= 0: granted = True if "permissions" in registrar_item: granted = self.permission_center.check_permissions(msg.getFrom().getStripped(), registrar_item["permissions"]) if granted: m = registrar_item["method"] resp = m(msg) reply_stanza.setBody(resp) else: reply_stanza.setBody("Sorry, you do not have the needed permission to execute this command.") loop = False break if not loop: break return reply_stanza def build_help(self, msg): """ build the help message according to the current registrar @type msg: xmpp.Protocol.Message @param msg: the received message @return the string containing the help message """ resp = ARCHIPEL_MESSAGING_HELP_MESSAGE for registrar_item in self.messages_registrar: if not "ignore" in registrar_item: granted = True if "permissions" in registrar_item: granted = self.permission_center.check_permissions(msg.getFrom().getStripped(), registrar_item["permissions"]) if granted: cmds = str(registrar_item["commands"]) desc = registrar_item["description"] params = registrar_item["parameters"] params_string = "" for p in params: params_string += "%s: %s\n" % (p["name"], p["description"]) if params_string == "": params_string = "No parameters" else: params_string = params_string[:-1] resp += "%s: %s\n%s\n\n" % (cmds, desc, params_string) return resp ### Permission IQ def process_permission_iq(self, conn, iq): """ this method is invoked when a ARCHIPEL_NS_PERMISSIONS IQ is received. it understands IQ of type: - list - get - set @type conn: xmpp.Dispatcher @param conn: ths instance of the current connection that send the stanza @type iq: xmpp.Protocol.Iq @param iq: the received IQ """ reply = None action = self.check_acp(conn, iq) if not action == "getown": self.check_perm(conn, iq, action, -1, prefix="permission_") if action == "list": reply = self.iq_list_permission(iq) elif action == "set": reply = self.iq_set_permission(iq, onlyown=False) elif action == "setown": reply = self.iq_set_permission(iq, onlyown=True) elif action == "get": reply = self.iq_get_permission(iq, onlyown=False) elif action == "getown": reply = self.iq_get_permission(iq, onlyown=True) if reply: conn.send(reply) raise xmpp.protocol.NodeProcessed def iq_set_permission(self, iq, onlyown): """ set a list of permission @type iq: xmpp.Node @param iq: the original request IQ @type onlyown: Boolean @param onlyown: if True, will raise an exception if user trying to set permission for other user """ try: reply = iq.buildReply("result") errors = [] perms = iq.getTag("query").getTag("archipel").getTags(name="permission") if onlyown: for perm in perms: if not perm.getAttr("permission_target") == iq.getFrom().getStripped(): raise Exception("You cannot set permissions of other users") perm_targets = [] for perm in perms: perm_type = perm.getAttr("permission_type") perm_target = perm.getAttr("permission_target") perm_name = perm.getAttr("permission_name") perm_value = perm.getAttr("permission_value") if perm_type == "role": if perm_value.upper() in ("1", "TRUE", "YES", "Y"): if not self.permission_center.grant_permission_to_role(perm_name, perm_target): errors.append("cannot grant permission %s on role %s" % (perm_name, perm_target)) else: if not self.permission_center.revoke_permission_to_role(perm_name, perm_target): errors.append("cannot revoke permission %s on role %s" % (perm_name, perm_target)) elif perm_type == "user": if perm_value.upper() in ("1", "TRUE", "YES", "Y", "OUI", "O"): self.log.info("granting permission %s to user %s" % (perm_name, perm_target)) if not self.permission_center.grant_permission_to_user(perm_name, perm_target): errors.append("cannot grant permission %s on user %s" % (perm_name, perm_target)) else: self.log.info("revoking permission %s to user %s" % (perm_name, perm_target)) if not self.permission_center.revoke_permission_to_user(perm_name, perm_target): errors.append("cannot revoke permission %s on user %s" % (perm_name, perm_target)) if perm_name == "presence": if self.permission_center.check_permission(perm_target, "presence"): self.authorize(xmpp.JID(perm_target)) else: self.unauthorize(xmpp.JID(perm_target)) if not perm_target in perm_targets: perm_targets.append(perm_target) if len(errors) > 0: reply = build_error_iq(self, str(errors), iq, ARCHIPEL_NS_PERMISSION_ERROR) for target in perm_targets: self.push_change("permissions", target) except Exception as ex: reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_SET_PERMISSIONS) return reply def iq_get_permission(self, iq, onlyown): """ return the list of permissions of a user @type iq: xmpp.Node @param iq: the original request IQ @type onlyown: Boolean @param onlyown: if True, will raise an exception if user trying to set permission for other user """ try: reply = iq.buildReply("result") nodes = [] perm_type = iq.getTag("query").getTag("archipel").getAttr("permission_type") perm_target = iq.getTag("query").getTag("archipel").getAttr("permission_target") if onlyown and not perm_target == iq.getFrom().getStripped(): raise Exception("You cannot get permissions of other users") if perm_type == "user": permissions = self.permission_center.get_user_permissions(perm_target) if permissions: for perm in permissions: nodes.append(xmpp.Node(tag="permission", attrs={"name": perm.name})) reply.setQueryPayload(nodes) except Exception as ex: reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_GET_PERMISSIONS) return reply def iq_list_permission(self, iq): """ return the list of available permission @type iq: xmpp.Node @param iq: the original request IQ """ try: reply = iq.buildReply("result") nodes = [] permissions = self.permission_center.get_permissions() if permissions: for perm in permissions: nodes.append(xmpp.Node(tag="permission", attrs={"name": perm.name, "default": perm.defaultValue, "description": perm.description})) reply.setQueryPayload(nodes) except Exception as ex: reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_LIST_PERMISSIONS) return reply ### Loop def loop(self): """ This is the main loop of the client """ if self.loop_status == ARCHIPEL_XMPP_LOOP_ON: self.perform_hooks("HOOK_ARCHIPELENTITY_XMPP_LOOP_STARTED") while not self.loop_status == ARCHIPEL_XMPP_LOOP_OFF: try: if self.loop_status == ARCHIPEL_XMPP_LOOP_REMOVE_USER: self.process_inband_unregistration() return if self.loop_status == ARCHIPEL_XMPP_LOOP_ON: if self.xmppclient.isConnected(): self.xmppclient.Process(3) elif self.loop_status == ARCHIPEL_XMPP_LOOP_RESTART: self.perform_hooks("HOOK_ARCHIPELENTITY_XMPP_LOOP_STARTED") if self.xmppclient.isConnected(): self.xmppclient.disconnect() time.sleep(1.0) self.connect() except Exception as ex: if str(ex).find('User removed') > -1: # ok, weird. self.log.info("LOOP EXCEPTION: Account has been removed from server") self.loop_status = ARCHIPEL_XMPP_LOOP_OFF elif self.auto_reconnect: self.log.error("LOOP EXCEPTION : Disconnected from server. Trying to reconnect in 5 five seconds") t, v, tr = sys.exc_info() self.log.error("TRACEBACK: %s" % traceback.format_exception(t, v, tr)) self.loop_status = ARCHIPEL_XMPP_LOOP_RESTART time.sleep(5.0) else: self.log.error("LOOP EXCEPTION : End of loop forced by exception : %s" % str(ex)) t, v, tr = sys.exc_info() self.log.error("TRACEBACK: %s" % traceback.format_exception(t, v, tr)) self.loop_status = ARCHIPEL_XMPP_LOOP_OFF self.perform_hooks("HOOK_ARCHIPELENTITY_XMPP_LOOP_STOPPED") if self.xmppclient.isConnected(): self.xmppclient.disconnect()