Hello community, here is the log from the commit of package python-nbxmpp for openSUSE:Factory checked in at 2016-10-20 23:08:23 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-nbxmpp (Old) and /work/SRC/openSUSE:Factory/.python-nbxmpp.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-nbxmpp" Changes: -------- --- /work/SRC/openSUSE:Factory/python-nbxmpp/python-nbxmpp.changes 2015-09-08 17:42:18.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-nbxmpp.new/python-nbxmpp.changes 2016-10-20 23:09:31.000000000 +0200 @@ -1,0 +2,15 @@ +Thu Oct 13 12:02:32 UTC 2016 - sor.alexei@meowr.ru + +- Update to version 0.5.4: + * Fix SCRAM authentication. + * Fix BOSH connection with UTF-8 messages. + * Fix smacks implementation. + * Use uuid in stanza ids. + +------------------------------------------------------------------- +Sun May 8 07:03:11 UTC 2016 - arun@gmx.de + +- specfile: + * updated source url to files.pythonhosted.org + +------------------------------------------------------------------- Old: ---- nbxmpp-0.5.3.tar.gz New: ---- nbxmpp-0.5.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-nbxmpp.spec ++++++ --- /var/tmp/diff_new_pack.7dTqY0/_old 2016-10-20 23:09:32.000000000 +0200 +++ /var/tmp/diff_new_pack.7dTqY0/_new 2016-10-20 23:09:32.000000000 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-nbxmpp # -# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,20 +18,21 @@ %define _name nbxmpp Name: python-nbxmpp -Version: 0.5.3 +Version: 0.5.4 Release: 0 Summary: XMPP library by Gajim team License: GPL-3.0+ Group: Development/Languages/Python -Url: http://python-nbxmpp.gajim.org/ -Source: https://pypi.python.org/packages/source/n/%{_name}/%{_name}-%{version}.tar.gz +Url: https://python-nbxmpp.gajim.org/ +Source: https://files.pythonhosted.org/packages/source/n/%{_name}/%{_name}-%{version}.tar.gz BuildRequires: fdupes BuildRequires: python-devel BuildRoot: %{_tmppath}/%{name}-%{version}-build -%if 0%{?suse_version} && 0%{?suse_version} <= 1110 -%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} -%else +%if 0%{?suse_version} >= 1120 BuildArch: noarch +%else +%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} +%py_requires %endif %description @@ -48,12 +49,14 @@ %prep %setup -q -n %{_name}-%{version} +rm doc/examples/*.orig %build python setup.py build %install -python setup.py install --root %{buildroot} --prefix=%{_prefix} +python setup.py install \ + --root %{buildroot} --prefix=%{_prefix} %fdupes %{buildroot} %files ++++++ nbxmpp-0.5.3.tar.gz -> nbxmpp-0.5.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/ChangeLog new/nbxmpp-0.5.4/ChangeLog --- old/nbxmpp-0.5.3/ChangeLog 2015-07-16 21:07:45.000000000 +0200 +++ new/nbxmpp-0.5.4/ChangeLog 2016-09-04 13:45:11.000000000 +0200 @@ -1,3 +1,10 @@ +python-nbxmpp 0.5.4 (04 September 2016) + + * Fix SCRAM authentication + * Fix BOSH connection with UTF-8 messages + * Fix smacks implementation + * Use uuid in stanza ids + python-nbxmpp 0.5.3 (13 July 2015) * Fix receiving long utf8 strings under py3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/PKG-INFO new/nbxmpp-0.5.4/PKG-INFO --- old/nbxmpp-0.5.3/PKG-INFO 2015-07-16 21:10:36.000000000 +0200 +++ new/nbxmpp-0.5.4/PKG-INFO 2016-09-04 13:50:47.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: nbxmpp -Version: 0.5.3 +Version: 0.5.4 Summary: Non blocking Jabber/XMPP module Home-page: http://python-nbxmpp.gajim.org Author: Yann Leboulanger diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/README new/nbxmpp-0.5.4/README --- old/nbxmpp-0.5.3/README 2014-04-09 17:31:59.000000000 +0200 +++ new/nbxmpp-0.5.4/README 2015-07-26 13:10:28.000000000 +0200 @@ -11,7 +11,7 @@ Usage -You have to 'import xmpppy' in your program. Then look at examples in ths doc +You have to 'import xmpppy' in your program. Then look at examples in the doc folder. 2012-05-08 Yann Leboulanger diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/doc/examples/xsend.py new/nbxmpp-0.5.4/doc/examples/xsend.py --- old/nbxmpp-0.5.3/doc/examples/xsend.py 2014-04-09 17:31:59.000000000 +0200 +++ new/nbxmpp-0.5.4/doc/examples/xsend.py 2016-05-15 21:07:20.000000000 +0200 @@ -1,11 +1,14 @@ -#!/usr/bin/python +#!/usr/bin/python3 import sys import os import nbxmpp import time import logging -import gobject +try: + from gi.repository import GObject as gobject +except Exception: + import gobject consoleloghandler = logging.StreamHandler() root_log = logging.getLogger('nbxmpp') @@ -13,7 +16,7 @@ root_log.addHandler(consoleloghandler) if len(sys.argv) < 2: - print "Syntax: xsend JID text" + print("Syntax: xsend JID text") sys.exit(0) to_jid = sys.argv[1] @@ -28,7 +31,7 @@ for mandatory in ['jid', 'password']: if mandatory not in jidparams.keys(): open(os.environ['HOME']+'/.xsend','w').write('#Uncomment fields before use and type in correct credentials.\n#JID=romeo@montague.net/resource (/resource is optional)\n#PASSWORD=juliet\n') - print 'Please point ~/.xsend config file to valid JID for sending messages.' + print('Please point ~/.xsend config file to valid JID for sending messages.') sys.exit(0) class Connection: @@ -40,20 +43,20 @@ def on_auth(self, con, auth): if not auth: - print 'could not authenticate!' + print('could not authenticate!') sys.exit() - print 'authenticated using', auth + print('authenticated using ' + auth) self.send_message(to_jid, text) def on_connected(self, con, con_type): - print 'connected with', con_type + print('connected with ' + con_type) auth = self.client.auth(self.jid.getNode(), self.password, resource=self.jid.getResource(), sasl=1, on_auth=self.on_auth) def get_password(self, cb, mech): cb(self.password) def on_connection_failed(self): - print 'could not connect!' + print('could not connect!') def _event_dispatcher(self, realm, event, data): pass @@ -61,11 +64,11 @@ def connect(self): idle_queue = nbxmpp.idlequeue.get_idlequeue() self.client = nbxmpp.NonBlockingClient(self.jid.getDomain(), idle_queue, caller=self) - self.con = self.client.connect(self.on_connected, self.on_connection_failed, secure_tuple=('tls', '', '', None)) + self.con = self.client.connect(self.on_connected, self.on_connection_failed, secure_tuple=('tls', '', '', None, None)) def send_message(self, to_jid, text): id_ = self.client.send(nbxmpp.protocol.Message(to_jid, text, typ='chat')) - print 'sent message with id', id_ + print('sent message with id ' + id_) gobject.timeout_add(1000, self.quit) def quit(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/doc/examples/xsend.py.orig new/nbxmpp-0.5.4/doc/examples/xsend.py.orig --- old/nbxmpp-0.5.3/doc/examples/xsend.py.orig 1970-01-01 01:00:00.000000000 +0100 +++ new/nbxmpp-0.5.4/doc/examples/xsend.py.orig 2016-05-15 21:04:13.000000000 +0200 @@ -0,0 +1,89 @@ +#!/usr/bin/python3 + +import sys +import os +import nbxmpp +import time +import logging +try: + from gi.repository import GObject as gobject +except Exception: + import gobject + +consoleloghandler = logging.StreamHandler() +root_log = logging.getLogger('nbxmpp') +root_log.setLevel('DEBUG') +root_log.addHandler(consoleloghandler) + +if len(sys.argv) < 2: + print("Syntax: xsend JID text") + sys.exit(0) + +to_jid = sys.argv[1] +text = ' '.join(sys.argv[2:]) + +jidparams = {} +if os.access(os.environ['HOME'] + '/.xsend', os.R_OK): + for ln in open(os.environ['HOME'] + '/.xsend').readlines(): + if not ln[0] in ('#', ';'): + key, val = ln.strip().split('=', 1) + jidparams[key.lower()] = val +for mandatory in ['jid', 'password']: + if mandatory not in jidparams.keys(): + open(os.environ['HOME']+'/.xsend','w').write('#Uncomment fields before use and type in correct credentials.\n#JID=romeo@montague.net/resource (/resource is optional)\n#PASSWORD=juliet\n') + print('Please point ~/.xsend config file to valid JID for sending messages.') + sys.exit(0) + +class Connection: + def __init__(self): + self.jid = nbxmpp.protocol.JID(jidparams['jid']) + self.password = jidparams['password'] + self.sm = nbxmpp.Smacks(self) # Stream Management + self.client_cert = None + + def on_auth(self, con, auth): + if not auth: + print('could not authenticate!') + sys.exit() + print('authenticated using ' + auth) + ri = self.client.initRoster() + self.roster = self.client.getRoster(force=True) + id_ = self.roster.Request(force=True) + roster_list = self.roster.getItems() + self.send_message(to_jid, text) + + def on_connected(self, con, con_type): + print('connected with ' + con_type) + auth = self.client.auth(self.jid.getNode(), self.password, resource=self.jid.getResource(), sasl=1, on_auth=self.on_auth) + + def get_password(self, cb, mech): + cb(self.password) + + def on_connection_failed(self): + print('could not connect!') + + def _event_dispatcher(self, realm, event, data): + pass + + def connect(self): + idle_queue = nbxmpp.idlequeue.get_idlequeue() + self.client = nbxmpp.NonBlockingClient(self.jid.getDomain(), idle_queue, caller=self) + self.con = self.client.connect(self.on_connected, self.on_connection_failed, secure_tuple=('tls', '', '', None, None)) + + def send_message(self, to_jid, text): + id_ = self.client.send(nbxmpp.protocol.Message(to_jid, text, typ='chat')) + print('sent message with id ' + id_) + gobject.timeout_add(1000, self.quit) + + def quit(self): + self.disconnect() + ml.quit() + + def disconnect(self): + self.client.start_disconnect() + + +con = Connection() +con.connect() +ml = gobject.MainLoop() +ml.run() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/nbxmpp/__init__.py new/nbxmpp-0.5.4/nbxmpp/__init__.py --- old/nbxmpp-0.5.3/nbxmpp/__init__.py 2015-07-16 21:05:50.000000000 +0200 +++ new/nbxmpp-0.5.4/nbxmpp/__init__.py 2016-09-04 13:48:01.000000000 +0200 @@ -17,4 +17,4 @@ from .plugin import PlugIn from .smacks import Smacks -__version__ = "0.5.3" +__version__ = "0.5.4" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/nbxmpp/auth_nb.py new/nbxmpp-0.5.4/nbxmpp/auth_nb.py --- old/nbxmpp-0.5.3/nbxmpp/auth_nb.py 2015-06-14 14:26:56.000000000 +0200 +++ new/nbxmpp-0.5.4/nbxmpp/auth_nb.py 2015-08-31 13:40:55.000000000 +0200 @@ -110,7 +110,7 @@ :param username: XMPP username :param password: XMPP password :param on_sasl: Callback, will be called after each SASL auth-step. - :param channel_binding: TLS channel binding data, None if the + :param channel_binding: TLS channel binding data, None if the binding data is not available :param auth_mechs: Set of valid authentication mechanisms. Possible entries are: @@ -184,11 +184,11 @@ return self.mecs = set( - mec.getData() - for mec + mec.getData() + for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags('mechanism') ) & self.enabled_auth_mechs - + # Password based authentication mechanism ordered by strength. # If the server supports a mechanism disable all weaker mechanisms. password_auth_mechs_strength = ['SCRAM-SHA-1-PLUS', 'SCRAM-SHA-1', @@ -422,8 +422,8 @@ r = 'c=' + scram_base64(self.scram_gs2) else: # Channel binding data goes in here too. - r = 'c=' + scram_base64(bytes(self.scram_gs2) - + self.channel_binding) + r = 'c=' + scram_base64(self.scram_gs2.encode('utf-8') + + self.channel_binding) r += ',r=' + data['r'] self.scram_soup += r self.scram_soup = self.scram_soup.encode('utf-8') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/nbxmpp/dispatcher_nb.py new/nbxmpp-0.5.4/nbxmpp/dispatcher_nb.py --- old/nbxmpp-0.5.3/nbxmpp/dispatcher_nb.py 2014-04-09 17:32:20.000000000 +0200 +++ new/nbxmpp-0.5.4/nbxmpp/dispatcher_nb.py 2015-12-17 19:47:08.000000000 +0100 @@ -25,6 +25,7 @@ import sys import locale import re +import uuid from xml.parsers.expat import ExpatError from .plugin import PlugIn from .protocol import (NS_STREAMS, NS_XMPP_STREAMS, NS_HTTP_BIND, Iq, Presence, @@ -38,7 +39,6 @@ #: default timeout to wait for response for our id DEFAULT_TIMEOUT_SECONDS = 25 -outgoingID = 0 XML_DECLARATION = '<?xml version=\'1.0\'?>' @@ -120,9 +120,7 @@ self.invalid_chars_re = re.compile(r) def getAnID(self): - global outgoingID - outgoingID += 1 - return repr(outgoingID) + return str(uuid.uuid4()) def dumpHandlers(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/nbxmpp/idlequeue.py new/nbxmpp-0.5.4/nbxmpp/idlequeue.py --- old/nbxmpp-0.5.3/nbxmpp/idlequeue.py 2014-04-09 17:32:20.000000000 +0200 +++ new/nbxmpp-0.5.4/nbxmpp/idlequeue.py 2015-09-01 22:04:43.000000000 +0200 @@ -42,7 +42,7 @@ elif os.name == 'posix': import fcntl -if sys.version_info[0] == 2: +if sys.version_info[0] == 2 or not HAVE_GLIB: FLAG_WRITE = 20 # write only FLAG_READ = 19 # read only FLAG_READ_WRITE = 23 # read and write @@ -568,7 +568,7 @@ def process(self): self._check_time_events() - if sys.version_info[0] == 2: + if sys.version_info[0] == 2 or not HAVE_GLIB: def current_time(self): return gobject.get_current_time() * 1e6 else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/nbxmpp/smacks.py new/nbxmpp-0.5.4/nbxmpp/smacks.py --- old/nbxmpp-0.5.3/nbxmpp/smacks.py 2014-04-09 17:33:08.000000000 +0200 +++ new/nbxmpp-0.5.4/nbxmpp/smacks.py 2015-12-17 19:42:45.000000000 +0100 @@ -17,6 +17,7 @@ self.out_h = 0 # Outgoing stanzas handled self.in_h = 0 # Incoming stanzas handled self.uqueue = [] # Unhandled stanzas queue + self.old_uqueue = [] # Unhandled stanzas queue of the last session self.session_id = None self.resumption = False # If server supports resume # Max number of stanzas in queue before making a request @@ -38,7 +39,7 @@ xmlns=NS_STREAM_MGMT) owner.Dispatcher.RegisterHandler('a', self.check_ack, xmlns=NS_STREAM_MGMT) - owner.Dispatcher.RegisterHandler('resumed', self.check_ack, + owner.Dispatcher.RegisterHandler('resumed', self.check_resume, xmlns=NS_STREAM_MGMT) owner.Dispatcher.RegisterHandler('failed', self.error_handling, xmlns=NS_STREAM_MGMT) @@ -61,6 +62,7 @@ # Every time we attempt to negociate, we must erase all previous info # about any previous session self.uqueue = [] + self.old_uqueue = [] self.in_h = 0 self.out_h = 0 self.session_id = None @@ -75,6 +77,8 @@ self.resuming = False log.error('Attempted to resume without a valid session id ') return + self.old_uqueue = self.uqueue #save old messages in an extra "queue" to avoid race conditions + self.uqueue = [] resume = Acks() resume.buildResume(self.in_h, self.session_id) self._owner.Connection.send(resume, False) @@ -102,19 +106,48 @@ h = int(h) diff = self.out_h - h - if len(self.uqueue) < diff or diff < 0: - log.error('Server and client number of stanzas handled mismatch ') + if diff < 0: + log.error('Server and client number of stanzas handled mismatch (our h: %d, server h: %d)' % (self.out_h, h)) + while (len(self.uqueue)): #don't accumulate all messages in this case (they would otherwise all be resent on the next reconnect) + self.uqueue.pop(0) + elif len(self.uqueue) < diff: + log.error('Server and client number of stanzas handled mismatch (our h: %d, server h: %d)' % (self.out_h, h)) else: + log.debug('Got ack for outgoing stanzas (our h: %d, server h: %d), removing %d messages from queue...' % (self.out_h, h, len(self.uqueue) - diff)) while (len(self.uqueue) > diff): self.uqueue.pop(0) + + def check_resume(self, disp, stanza): + ''' + Checks if the number of stanzas sent are the same as the + number of stanzas received by the server. Resends stanzas not received + by the server in the last session. + ''' + h = stanza.getAttr('h') + if not h: + log.error('Server did not send h attribute') + return + h = int(h) + diff = self.out_h - h - if stanza.getName() == 'resumed': - self.enabled = True - self.resuming = True - self.con.set_oldst() - if self.uqueue != []: - for i in self.uqueue: - self._owner.Connection.send(i, False) + if diff < 0: + log.error('Server and client number of stanzas handled mismatch on session resumption (our h: %d, server h: %d)' % (self.out_h, h)) + self.old_uqueue = [] #that's weird, but we don't resend this stanzas if the server says we don't need to + elif len(self.old_uqueue) < diff: + log.error('Server and client number of stanzas handled mismatch on session resumption (our h: %d, server h: %d)' % (self.out_h, h)) + else: + log.info('Removing %d already received stanzas from old outgoing queue (our h: %d, server h: %d, remaining in queue: %d)' % (len(self.old_uqueue) - diff, self.out_h, h, diff)) + while (len(self.old_uqueue) > diff): + self.old_uqueue.pop(0) + + self.enabled = True + self.resuming = True + self.con.set_oldst() + if self.old_uqueue != []: + log.info('Session resumed, replaying %s stanzas...' % len(self.old_uqueue)) + for i in self.old_uqueue: + self._owner.Connection.send(i, False) + self.old_uqueue = [] def error_handling(self, disp, stanza): # If the server doesn't recognize previd, forget about resuming diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/nbxmpp/tls_nb.py new/nbxmpp-0.5.4/nbxmpp/tls_nb.py --- old/nbxmpp-0.5.3/nbxmpp/tls_nb.py 2014-04-09 17:33:30.000000000 +0200 +++ new/nbxmpp-0.5.4/nbxmpp/tls_nb.py 2016-04-02 17:59:03.000000000 +0200 @@ -267,9 +267,9 @@ self.cacerts = cacerts self.mycerts = mycerts if cipher_list is None: - self.cipher_list = 'HIGH:!aNULL:RC4-SHA' + self.cipher_list = b'HIGH:!aNULL:RC4-SHA' else: - self.cipher_list = cipher_list + self.cipher_list = cipher_list.encode('ascii') if tls_version is None: self.tls_version = '1.0' else: @@ -350,7 +350,7 @@ cert = ''.join(lines[begin:i+2]) try: x509cert = OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) + OpenSSL.crypto.FILETYPE_PEM, cert.encode('ascii', 'ignore')) cert_store.add_cert(x509cert) except OpenSSL.crypto.Error as exception_obj: if logg: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/nbxmpp/transports_nb.py new/nbxmpp-0.5.4/nbxmpp/transports_nb.py --- old/nbxmpp-0.5.3/nbxmpp/transports_nb.py 2015-07-16 21:07:52.000000000 +0200 +++ new/nbxmpp-0.5.4/nbxmpp/transports_nb.py 2016-07-04 21:48:04.000000000 +0200 @@ -355,7 +355,7 @@ self._sock = socket.socket(*conn_5tuple[:3]) except socket.error as e: self._on_connect_failure('NonBlockingTCP Connect: Error while creating\ - socket: %s' % atr(e)) + socket: %s' % str(e)) return self._send = self._sock.send @@ -752,11 +752,11 @@ if 'Connection' in headers and headers['Connection'].strip()=='close': self.close_current_connection = True - if self.expected_length > len(httpbody): + if self.expected_length > len(httpbody.encode('utf-8')): # If we haven't received the whole HTTP mess yet, let's end the thread. # It will be finnished from one of following recvs on plugged socket. log.info('not enough bytes in HTTP response - %d expected, got %d' % - (self.expected_length, len(httpbody))) + (self.expected_length, len(httpbody.encode('utf-8')))) else: # First part of buffer has been extraced and is going to be handled, # remove it from buffer @@ -821,7 +821,7 @@ for dummy in header: row = dummy.split(' ', 1) headers[row[0][:-1]] = row[1] - body_size = headers['Content-Length'] + body_size = int(headers['Content-Length']) rest_splitted = splitted[2:] while (len(httpbody) < body_size) and rest_splitted: # Complete httpbody until it has the announced size diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbxmpp-0.5.3/setup.py new/nbxmpp-0.5.4/setup.py --- old/nbxmpp-0.5.3/setup.py 2015-07-16 21:05:58.000000000 +0200 +++ new/nbxmpp-0.5.4/setup.py 2016-09-04 13:48:08.000000000 +0200 @@ -3,7 +3,7 @@ from distutils.core import setup setup(name='nbxmpp', - version='0.5.3', + version='0.5.4', description='Non blocking Jabber/XMPP module', author='Yann Leboulanger', author_email='asterix@lagaule.org',