commit f27235b862b7e7e6b1b1dd8f07e427b3799e3aeb Author: Ralph Giles Date: Tue Jun 14 14:10:31 2005 +0000 Move the xmpp library to its new name. diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..66948df --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,10 @@ +Version 0.7 (unreleased) + + * first public release + * basic connection support + * event handling through callbacks + * stanza creation through local mini-DOM + * support SASL DIGEST-MD5 authentication + * support SASL PLAIN authentication + * support fallback to jabber:auth + * support resource binding diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000..b6f92f3 --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..17adef5 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +libstrophe XMPP client library +Copyright (C) 2005 OGG, LLC. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program (in a file named COPYING). If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. + +For information about purchasing an exemption for use with +proprietary software under terms other than the above, please +contact . diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..e11e78e --- /dev/null +++ b/README.txt @@ -0,0 +1,31 @@ +This is libstrophe, our experimental XMPP C client library. + +Our goals are: + + (a) usable quickly + (b) well documented + (c) reliable + (d) not suck + +And to implement the complete XMPP spec, but not the historical Jabber +bits. + +== Build Instructions == + +We use the 'scons' tool to build the library, unit tests, +documentation and examples. You'll need to obtain a copy +from http://www.scons.org/ or from your system distributor. + +Once scons is installed, invoke 'scons' in the top-level +directory to build the library. This will create a static +library (also in the top-level) directory which can be +linked into other programs. The public api is defined +in which is also in the top-level directory. + +Invoke 'scons test' in the top-level directory to execute +the unit and self tests. + +The examples/ directory contains some examples of how to +use the library; these may be helpful in addition to the +API documentation in doc/. + diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..4d90ae2 --- /dev/null +++ b/SConstruct @@ -0,0 +1,151 @@ +# -*- Mode: python -*- +# SCons build specification +# see http://www.scons.org if you do not have this tool + +# Copyright (C) 2005 OGG, LCC. All rights reserved. +# This software is provided AS-IS with no warranty, either express or +# implied. +# +# This software is distributed under license and may not be copied, +# modified or distributed except as expressly authorized under the +# terms of the license contained in the file LICENSE.txt in this +# distribution. + +# This source code is distributed under specific license +# terms. See LICENSE.txt for details. + +# invoke with 'scons' to build the library + +LIBXMPP_VERSION_MAJOR=0 +LIBXMPP_VERSION_MINOR=0 + +from os.path import join + +# TODO: should use lamda and map to work on python 1.5 +def path(prefix, list): return [join(prefix, x) for x in list] + +ExpatSources = Split(""" + xmlparse.c + xmltok.c + xmlrole.c +""") + +Sources = Split(""" + ctx.c + conn.c + parser.c + handler.c + auth.c + event.c + stanza.c + jid.c + sock.c + hash.c + sasl.c + sha1.c + md5.c + util.c +""") + +Headers = Split(""" + xmpp.h + common.h + sock.h + hash.h + sha1.h + md5.h + util.h +""") + +Examples = Split(""" + basic.c + active.c + roster.c +""") + +env = Environment() +if env['CC'] == 'gcc': + env.Append(CCFLAGS=["-g", "-Wall"]) + +expatenv = env.Copy() +# feature defs +expatenv.Append(CCFLAGS=" -DXML_DTD") +expatenv.Append(CCFLAGS=" -DXML_NS") +expatenv.Append(CCFLAGS=" -DXML_CONTEXT_BYTES=1024") +# sys config defs (these should be autodetected) +expatenv.Append(CCFLAGS=" -DBYTEORDER=4321") +expatenv.Append(CCFLAGS=" -DHAVE_MEMMOVE") +expatenv.Append(CCFLAGS=" -DHAVE_UNISTD") + +expat = expatenv.Library('expat', path(join('expat', 'lib'), ExpatSources)) +Default(expat) + +xmppenv = env.Copy() +xmppenv.Append(CPPPATH=['.', 'src', join('expat','lib')]) + +xmpp = xmppenv.Library('xmpp', path("src", Sources)) +Default(xmpp) + +exenv = xmppenv.Copy() +exenv.Append(LIBS=["xmpp", "expat"]) +exenv.Append(LIBPATH=["."]) +for e in path("examples", Examples): + example = exenv.Program(e) + Default(example) + +# generate a MSVC project for convenience +# TODO: this doesn't seem to be implemented? +#env.MSVSProject(target = 'xmpp' + env['MSVSPROJECTSUFFIX'], +# srcs = path("src", Sources), incs = path("src", Headers), +# variant = 'Release', buildtarget = xmpp) + +### unit and regression tests + +import unittest +from os import popen + +class RunnableTestCase(unittest.TestCase): + def __init__(self, file): + self.file = str(file) + unittest.TestCase.__init__(self) + def shortDescription(self): + return "Running '%s'" % self.file + def runTest(self): + '''C tests should return '0' on exit''' + test = popen(self.file) + self.failIf(test.close(), "unit '%s' did not exit normally" % self.file) + +def testcase_runner(target, source, env): + suite = unittest.TestSuite() + for test in source: + #suite.addTest(RunnableTestCase(test[0])) + suite.addTest(RunnableTestCase(test)) + result = unittest.TextTestRunner(verbosity=2).run(suite) + env.Execute(Touch(str(target[0]))) + +testenv = env.Copy() +testenv.Append(CPPPATH=['.', 'src', join('expat','lib')]) +testenv.Append(LIBS=['xmpp', 'expat']) +testenv.Append(LIBPATH=['.']) + +import SCons.Node +test_builder = Builder(action = testcase_runner, + multi = 1) + +testenv.Append(BUILDERS = {'TestCase' : test_builder}) + +# application source files for unit tests +tests = Split(""" + test_ctx.c + test_sock.c + test_hash.c + test_base64.c + test_sasl.c + test_jid.c +""") + +# build each test and add it to the 'test' pseudo target +for test in path("tests", tests): + tests = testenv.TestCase('test_stamp', testenv.Program(test)) +testenv.AlwaysBuild(tests) +testenv.Alias('test', tests) diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..b664a15 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,4 @@ +* thread safety: specifically the reference counting is NOT THREAD SAFE + and we should fix this later to make reference count operations atomic. + +* SRV lookups diff --git a/examples/active.c b/examples/active.c new file mode 100644 index 0000000..cfca183 --- /dev/null +++ b/examples/active.c @@ -0,0 +1,122 @@ +/* active.c +** XMPP client library -- basic usage example +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +/* This example demonstrates basic handler functions by printing out +** active resources on a jabberd 2.x server. This program requires +** an admin account on a jabberd 2.x account in order to run. +*/ + +#include +#include + +#include + +int handle_reply(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *query, *item; + char *type; + + type = xmpp_stanza_get_type(stanza); + if (strcmp("type", "error") == 0) + fprintf(stderr, "ERROR: query failed\n"); + else { + query = xmpp_stanza_get_child_by_name(stanza, "query"); + printf("Active Sessions:\n"); + for (item = xmpp_stanza_get_children(query); item; + item = xmpp_stanza_get_next(item)) + printf("\t %s\n", xmpp_stanza_get_attribute(item, "jid")); + printf("END OF LIST\n"); + } + + /* disconnect */ + xmpp_disconnect(conn); + + return 0; +} + +void conn_handler(xmpp_conn_t * const conn, const xmpp_conn_event_t status, + const int error, xmpp_stream_error_t * const stream_error, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + xmpp_stanza_t *iq, *query; + + if (status == XMPP_CONN_CONNECT) { + fprintf(stderr, "DEBUG: connected\n"); + + /* create iq stanza for request */ + iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, "iq"); + xmpp_stanza_set_type(iq, "get"); + xmpp_stanza_set_id(iq, "active1"); + xmpp_stanza_set_attribute(iq, "to", "xxxxxxxxx.com"); + + query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, "query"); + xmpp_stanza_set_ns(query, XMPP_NS_DISCO_ITEMS); + xmpp_stanza_set_attribute(query, "node", "sessions"); + + xmpp_stanza_add_child(iq, query); + + /* we can release the stanza since it belongs to iq now */ + xmpp_stanza_release(query); + + /* set up reply handler */ + xmpp_id_handler_add(conn, handle_reply, "active1", ctx); + + /* send out the stanza */ + xmpp_send(conn, iq); + + /* release the stanza */ + xmpp_stanza_release(iq); + } else { + fprintf(stderr, "DEBUG: disconnected\n"); + xmpp_stop(ctx); + } +} + +int main(int argc, char **argv) +{ + xmpp_ctx_t *ctx; + xmpp_conn_t *conn; + + if (argc != 4) { + fprintf(stderr, "Usage: active \n\n"); + return 1; + } + + /* create a context */ + ctx = xmpp_ctx_new(NULL, NULL); + + /* create a connection */ + conn = xmpp_conn_new(ctx); + + /* setup authentication information */ + xmpp_conn_set_jid(conn, argv[1]); + xmpp_conn_set_pass(conn, argv[2]); + + /* initiate connection */ + xmpp_connect_client(conn, argv[3], conn_handler, ctx); + + /* start the event loop */ + xmpp_run(ctx); + + /* release our connection and context */ + xmpp_conn_release(conn); + xmpp_ctx_free(ctx); + + return 0; +} diff --git a/examples/basic.c b/examples/basic.c new file mode 100644 index 0000000..8464212 --- /dev/null +++ b/examples/basic.c @@ -0,0 +1,66 @@ +/* basic.c +** XMPP client library -- basic usage example +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include + +#include + +void conn_handler(xmpp_conn_t * const conn, const xmpp_conn_event_t status, + const int error, xmpp_stream_error_t * const stream_error, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + + if (status == XMPP_CONN_CONNECT) { + fprintf(stderr, "DEBUG: connected\n"); + xmpp_disconnect(conn); + } + else { + fprintf(stderr, "DEBUG: disconnected\n"); + xmpp_stop(ctx); + } +} + +int main(int argc, char **argv) +{ + xmpp_ctx_t *ctx; + xmpp_conn_t *conn; + + if (argc != 4) { + fprintf(stderr, "Usage: basic \n\n"); + return 1; + } + + /* create a context */ + ctx = xmpp_ctx_new(NULL, NULL); + + /* create a connection */ + conn = xmpp_conn_new(ctx); + + /* setup authentication information */ + xmpp_conn_set_jid(conn, argv[1]); + xmpp_conn_set_pass(conn, argv[2]); + + /* initiate connection */ + xmpp_connect_client(conn, argv[3], conn_handler, ctx); + + /* start the event loop */ + xmpp_run(ctx); + + /* release our connection and context */ + xmpp_conn_release(conn); + xmpp_ctx_free(ctx); + + return 0; +} diff --git a/examples/roster.c b/examples/roster.c new file mode 100644 index 0000000..bc92859 --- /dev/null +++ b/examples/roster.c @@ -0,0 +1,127 @@ +/* roster.c +** XMPP client library -- handler example +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +/* This example demonstrates basic handler functions by printing out +** the user's roster. +*/ + +#include +#include + +#include + +int handle_reply(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *query, *item; + char *type, *name; + + type = xmpp_stanza_get_type(stanza); + if (strcmp("type", "error") == 0) + fprintf(stderr, "ERROR: query failed\n"); + else { + query = xmpp_stanza_get_child_by_name(stanza, "query"); + printf("Roster:\n"); + for (item = xmpp_stanza_get_children(query); item; + item = xmpp_stanza_get_next(item)) + if ((name = xmpp_stanza_get_attribute(item, "name"))) + printf("\t %s (%s) sub=%s\n", + name, + xmpp_stanza_get_attribute(item, "jid"), + xmpp_stanza_get_attribute(item, "subscription")); + else + printf("\t %s sub=%s\n", + xmpp_stanza_get_attribute(item, "jid"), + xmpp_stanza_get_attribute(item, "subscription")); + printf("END OF LIST\n"); + } + + /* disconnect */ + xmpp_disconnect(conn); + + return 0; +} + +void conn_handler(xmpp_conn_t * const conn, const xmpp_conn_event_t status, + const int error, xmpp_stream_error_t * const stream_error, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + xmpp_stanza_t *iq, *query; + + if (status == XMPP_CONN_CONNECT) { + fprintf(stderr, "DEBUG: connected\n"); + + /* create iq stanza for request */ + iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, "iq"); + xmpp_stanza_set_type(iq, "get"); + xmpp_stanza_set_id(iq, "roster1"); + + query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, "query"); + xmpp_stanza_set_ns(query, XMPP_NS_ROSTER); + + xmpp_stanza_add_child(iq, query); + + /* we can release the stanza since it belongs to iq now */ + xmpp_stanza_release(query); + + /* set up reply handler */ + xmpp_id_handler_add(conn, handle_reply, "roster1", ctx); + + /* send out the stanza */ + xmpp_send(conn, iq); + + /* release the stanza */ + xmpp_stanza_release(iq); + } else { + fprintf(stderr, "DEBUG: disconnected\n"); + xmpp_stop(ctx); + } +} + +int main(int argc, char **argv) +{ + xmpp_ctx_t *ctx; + xmpp_conn_t *conn; + + if (argc != 4) { + fprintf(stderr, "Usage: active \n\n"); + return 1; + } + + /* create a context */ + ctx = xmpp_ctx_new(NULL, NULL); + + /* create a connection */ + conn = xmpp_conn_new(ctx); + + /* setup authentication information */ + xmpp_conn_set_jid(conn, argv[1]); + xmpp_conn_set_pass(conn, argv[2]); + + /* initiate connection */ + xmpp_connect_client(conn, argv[3], conn_handler, ctx); + + /* start the event loop */ + xmpp_run(ctx); + + /* release our connection and context */ + xmpp_conn_release(conn); + xmpp_ctx_free(ctx); + + return 0; +} diff --git a/names.txt b/names.txt new file mode 100644 index 0000000..ad87a08 --- /dev/null +++ b/names.txt @@ -0,0 +1,9 @@ +Melissa's suggestions: + something to do with note passing in high school + something related to telephones/switchboards, etc + + ganglion + thalamus + +jack's suggestion: + eloquence and eloq for sure. libeloq etc. diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 0000000..fd40e33 --- /dev/null +++ b/src/auth.c @@ -0,0 +1,634 @@ +/* auth.c +** XMPP client library -- auth functions and handlers +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express or +** implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include +#include + +#include "xmpp.h" +#include "common.h" +#include "sasl.h" + +#define FEATURES_TIMEOUT 2000 /* 2 seconds */ +#define BIND_TIMEOUT 2000 /* 2 seconds */ +#define LEGACY_TIMEOUT 2000 /* 2 seconds */ + +static void _auth(xmpp_conn_t * const conn); +static void _handle_open_sasl(xmpp_conn_t * const conn); +static int _handle_missing_legacy(xmpp_conn_t * const conn, + void * const userdata); +static int _handle_legacy(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_features_sasl(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_digestmd5_default(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_digestmd5_challenge(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_digestmd5_rspauth(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); + +static int _handle_missing_features_sasl(xmpp_conn_t * const conn, + void * const userdata); +static int _handle_missing_bind(xmpp_conn_t * const conn, + void * const userdata); +static int _handle_bind(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); + +/* stream:features handlers */ + +static int _handle_missing_features(xmpp_conn_t * const conn, + void * const userdata) +{ + xmpp_debug(conn->ctx, "xmpp", "didn't get stream features"); + + return 0; +} + +static int _handle_features(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *child, *mech; + char *text; + + /* remove the handler that detects missing stream:features */ + xmpp_timed_handler_delete(conn, _handle_missing_features); + + /* check for TLS */ + child = xmpp_stanza_get_child_by_name(stanza, "starttls"); + if (child && (strcmp(xmpp_stanza_get_ns(child), XMPP_NS_TLS) == 0)) + conn->tls_support = 1; + + /* check for SASL */ + child = xmpp_stanza_get_child_by_name(stanza, "mechanisms"); + if (child && (strcmp(xmpp_stanza_get_ns(child), XMPP_NS_SASL) == 0)) { + for (mech = xmpp_stanza_get_children(child); mech; + mech = xmpp_stanza_get_next(mech)) { + if (strcmp(xmpp_stanza_get_name(mech), "mechanism") == 0) { + text = xmpp_stanza_get_text(mech); + if (strcasecmp(text, "PLAIN") == 0) + conn->sasl_support |= SASL_MASK_PLAIN; + else if (strcasecmp(text, "DIGEST-MD5") == 0) + conn->sasl_support |= SASL_MASK_DIGESTMD5; + + xmpp_free(conn->ctx, text); + } + } + } + + /* TODO: Implement TLS */ + + _auth(conn); + + return 0; +} + +/* returns the correct auth id for a component or a client. + * returned string must be freed by caller */ +static char *_get_authid(xmpp_conn_t * const conn) +{ + char *authid = NULL; + char *at; + + if (conn->type == XMPP_CLIENT) { + /* authid is the node portion of jid */ + if (!conn->jid) return NULL; + authid = xmpp_strdup(conn->ctx, conn->jid); + if (!authid) return NULL; + at = strchr(authid, '@'); + at[0] = 0; + } + + return authid; +} + +static int _handle_digestmd5_default(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *name; + int ret = 1; + + name = xmpp_stanza_get_name(stanza); + xmpp_debug(conn->ctx, "xmpp", + "handle digest-md5 (default) called for %s", name); + + if (strcmp(name, "failure") == 0) { + /* TODO: handle errors */ + xmpp_debug(conn->ctx, "xmpp", "SASL DIGEST-MD5 auth failed!"); + /* fall back to next auth method */ + _auth(conn); + + /* remove handler */ + ret = 0; + } else if (strcmp(name, "success") == 0) { + /* SASL DIGEST-MD5 auth successful, we need to restart the stream */ + xmpp_debug(conn->ctx, "xmpp", "SASL DIGEST-MD5 auth successful"); + + /* reset parser */ + parser_prepare_reset(conn, _handle_open_sasl); + + /* send stream tag */ + conn_open_stream(conn); + + /* remove handler */ + ret = 0; + } else { + /* got unexpected reply */ + xmpp_error(conn->ctx, "xmpp", "Got unexpected reply to SASL " + "DIGEST-MD5 authentication."); + xmpp_disconnect(conn); + + /* remove handler */ + ret = 0; + } + + return ret; +} + +/* handle the challenge phase of digest auth */ +static int _handle_digestmd5_challenge(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *text; + char *response; + xmpp_stanza_t *auth, *authdata; + char *name; + + name = xmpp_stanza_get_name(stanza); + xmpp_debug(conn->ctx, "xmpp",\ + "handle digest-md5 (challenge) called for %s", name); + + if (strcmp(name, "challenge") == 0) { + text = xmpp_stanza_get_text(stanza); + response = sasl_digest_md5(conn->ctx, text, conn->jid, conn->pass); + if (!response) { + disconnect_mem_error(conn); + return 0; + } + xmpp_free(conn->ctx, text); + + auth = xmpp_stanza_new(conn->ctx); + if (!auth) { + disconnect_mem_error(conn); + return 0; + } + xmpp_stanza_set_name(auth, "response"); + xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + disconnect_mem_error(conn); + return 0; + } + + xmpp_stanza_set_text(authdata, response); + xmpp_free(conn->ctx, response); + + xmpp_stanza_add_child(auth, authdata); + xmpp_stanza_release(authdata); + + handler_add(conn, _handle_digestmd5_rspauth, + XMPP_NS_SASL, NULL, NULL, NULL); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + } else { + return _handle_digestmd5_default(conn, stanza, userdata); + } + + /* remove ourselves */ + return 0; +} + +/* handle the rspauth phase of digest auth */ +static int _handle_digestmd5_rspauth(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *auth; + char *name; + + name = xmpp_stanza_get_name(stanza); + xmpp_debug(conn->ctx, "xmpp", + "handle digest-md5 (rspauth) called for %s", name); + + + if (strcmp(name, "challenge") == 0) { + /* assume it's an rspauth response */ + auth = xmpp_stanza_new(conn->ctx); + if (!auth) { + disconnect_mem_error(conn); + return 0; + } + xmpp_stanza_set_name(auth, "response"); + xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + } else { + return _handle_digestmd5_default(conn, stanza, userdata); + } + + return 1; +} + +static int _handle_plain(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *name; + int ret = 1; + + name = xmpp_stanza_get_name(stanza); + xmpp_debug(conn->ctx, "xmpp", "handle plain called for %s", name); + + /* the server should send a or stanza */ + if (strcmp(name, "failure") == 0) { + /* TODO: handle errors */ + + /* fall back to next auth method */ + _auth(conn); + } else if (strcmp(name, "success") == 0) { + /* SASL PLAIN auth successful, we need to restart the stream */ + xmpp_debug(conn->ctx, "xmpp", "SASL PLAIN auth successful"); + + /* reset parser */ + parser_prepare_reset(conn, _handle_open_sasl); + + /* remove this handler */ + ret = 0; + + /* send stream tag */ + conn_open_stream(conn); + } else { + /* got unexpected reply */ + xmpp_error(conn->ctx, "xmpp", "Got unexpected reply to SASL PLAIN "\ + "authentication."); + xmpp_disconnect(conn); + ret = 0; + } + + return ret; +} + +static xmpp_stanza_t *_make_sasl_auth(xmpp_conn_t * const conn, + const char * const mechanism) +{ + xmpp_stanza_t *auth; + + /* build auth stanza */ + auth = xmpp_stanza_new(conn->ctx); + if (auth) { + xmpp_stanza_set_name(auth, "auth"); + xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + xmpp_stanza_set_attribute(auth, "mechanism", mechanism); + } + + return auth; +} + +/* authenticate the connection + * this may get called multiple times. if any auth method fails, + * this will get called again until one auth method succeeds or every + * method fails */ +static void _auth(xmpp_conn_t * const conn) +{ + xmpp_stanza_t *auth, *authdata, *query, *child, *iq; + char *str, *authid; + + if (conn->sasl_support & SASL_MASK_DIGESTMD5) { + auth = _make_sasl_auth(conn, "DIGEST-MD5"); + if (!auth) { + disconnect_mem_error(conn); + return; + } + + handler_add(conn, _handle_digestmd5_challenge, + XMPP_NS_SASL, NULL, NULL, NULL); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + /* SASL DIGEST-MD5 was tried, unset flag */ + conn->sasl_support &= ~SASL_MASK_DIGESTMD5; + } else if (conn->sasl_support & SASL_MASK_PLAIN) { + auth = _make_sasl_auth(conn, "PLAIN"); + if (!auth) { + disconnect_mem_error(conn); + return; + } + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + disconnect_mem_error(conn); + return; + } + authid = _get_authid(conn); + if (!authid) { + disconnect_mem_error(conn); + return; + } + str = sasl_plain(conn->ctx, authid, conn->pass); + if (!str) { + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_text(authdata, str); + xmpp_free(conn->ctx, str); + + xmpp_stanza_add_child(auth, authdata); + xmpp_stanza_release(authdata); + + handler_add(conn, _handle_plain, + XMPP_NS_SASL, NULL, NULL, NULL); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + /* SASL PLAIN was tried */ + conn->sasl_support &= ~SASL_MASK_PLAIN; + } else if (conn->type == XMPP_CLIENT) { + /* legacy client authentication */ + + iq = xmpp_stanza_new(conn->ctx); + if (!iq) { + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(iq, "iq"); + xmpp_stanza_set_type(iq, "set"); + xmpp_stanza_set_id(iq, "_xmpp_auth1"); + + query = xmpp_stanza_new(conn->ctx); + if (!query) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(query, "query"); + xmpp_stanza_set_ns(query, XMPP_NS_AUTH); + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + + child = xmpp_stanza_new(conn->ctx); + if (!child) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(child, "username"); + xmpp_stanza_add_child(query, child); + xmpp_stanza_release(child); + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + str = xmpp_jid_node(conn->ctx, conn->jid); + xmpp_stanza_set_text(authdata, str); + xmpp_free(conn->ctx, str); + xmpp_stanza_add_child(child, authdata); + xmpp_stanza_release(authdata); + + child = xmpp_stanza_new(conn->ctx); + if (!child) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(child, "password"); + xmpp_stanza_add_child(query, child); + xmpp_stanza_release(child); + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_text(authdata, conn->pass); + xmpp_stanza_add_child(child, authdata); + xmpp_stanza_release(authdata); + + child = xmpp_stanza_new(conn->ctx); + if (!child) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(child, "resource"); + xmpp_stanza_add_child(query, child); + xmpp_stanza_release(child); + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + str = xmpp_jid_resource(conn->ctx, conn->jid); + if (str) + xmpp_stanza_set_text(authdata, str); + xmpp_free(conn->ctx, str); + xmpp_stanza_add_child(child, authdata); + xmpp_stanza_release(authdata); + + handler_add_id(conn, _handle_legacy, "_xmpp_auth1", NULL); + handler_add_timed(conn, _handle_missing_legacy, + LEGACY_TIMEOUT, NULL); + + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + } +} + + +/* this is called on initial stream open */ +void auth_handle_open(xmpp_conn_t * const conn) +{ + /* reset all timed handlers */ + handler_reset_timed(conn, 0); + + /* setup handlers for incoming */ + handler_add(conn, _handle_features, + NULL, "stream:features", NULL, NULL); + handler_add_timed(conn, _handle_missing_features, + FEATURES_TIMEOUT, NULL); +} + +/* called when stream:stream tag received after SASL auth */ +static void _handle_open_sasl(xmpp_conn_t * const conn) +{ + xmpp_debug(conn->ctx, "xmpp", "Reopened stream successfully."); + + /* setup stream:features handlers */ + handler_add(conn, _handle_features_sasl, + NULL, "stream:features", NULL, NULL); + handler_add_timed(conn, _handle_missing_features_sasl, + FEATURES_TIMEOUT, NULL); +} + +static int _handle_features_sasl(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *bind, *iq; + + /* remove missing features handler */ + xmpp_timed_handler_delete(conn, _handle_missing_features_sasl); + + /* we are expecting */ + bind = xmpp_stanza_get_child_by_name(stanza, "bind"); + if (bind && strcmp(xmpp_stanza_get_ns(bind), XMPP_NS_BIND) == 0) { + /* bind resource */ + + /* setup response handlers */ + handler_add_id(conn, _handle_bind, "_xmpp_bind1", NULL); + handler_add_timed(conn, _handle_missing_bind, + BIND_TIMEOUT, NULL); + + /* send bind response */ + /* FIXME: this should detect whether a resource is present in + conn->jid and bind that instead. right now we let the server bind + us */ + iq = xmpp_stanza_new(conn->ctx); + if (!iq) { + disconnect_mem_error(conn); + return 0; + } + + xmpp_stanza_set_name(iq, "iq"); + xmpp_stanza_set_type(iq, "set"); + xmpp_stanza_set_id(iq, "_xmpp_bind1"); + + bind = xmpp_stanza_copy(bind); + if (!bind) { + disconnect_mem_error(conn); + return 0; + } + xmpp_stanza_add_child(iq, bind); + xmpp_stanza_release(bind); + + /* send bind request */ + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + } else { + /* can't bind, disconnect */ + xmpp_error(conn->ctx, "xmpp", "Stream features does not allow "\ + "resource bind."); + xmpp_disconnect(conn); + } + + return 0; +} + +static int _handle_missing_features_sasl(xmpp_conn_t * const conn, + void * const userdata) +{ + xmpp_error(conn->ctx, "xmpp", "Did not receive stream features "\ + "after SASL authentication."); + xmpp_disconnect(conn); + return 0; +} + +static int _handle_bind(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *type; + + /* delete missing bind handler */ + xmpp_timed_handler_delete(conn, _handle_missing_bind); + + /* server has replied to bind request */ + type = xmpp_stanza_get_type(stanza); + if (!type) { + xmpp_error(conn->ctx, "xmpp", "Server sent malformed bind reply."); + xmpp_disconnect(conn); + } else if (strcmp(type, "error") == 0) { + xmpp_error(conn->ctx, "xmpp", "Binding failed."); + xmpp_disconnect(conn); + } else if (strcmp(type, "result") == 0) { + /* TODO: extract resource if present */ + xmpp_debug(conn->ctx, "xmpp", "Bind successful."); + conn->authenticated = 1; + + /* call connection handler */ + conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata); + } + + return 0; +} + +static int _handle_missing_bind(xmpp_conn_t * const conn, + void * const userdata) +{ + xmpp_error(conn->ctx, "xmpp", "Server did not reply to bind request."); + xmpp_disconnect(conn); + return 0; +} + +static int _handle_legacy(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *type, *name; + + /* delete missing handler */ + xmpp_timed_handler_delete(conn, _handle_missing_legacy); + + /* server responded to legacy auth request */ + type = xmpp_stanza_get_type(stanza); + name = xmpp_stanza_get_name(stanza); + if (!type || strcmp(name, "iq") != 0) { + xmpp_error(conn->ctx, "xmpp", "Server sent us an unexpected response "\ + "to legacy authentication request."); + xmpp_disconnect(conn); + } else if (strcmp(type, "error") == 0) { + /* legacy client auth failed, no more fallbacks */ + xmpp_error(conn->ctx, "xmpp", "Legacy client authentication failed."); + xmpp_disconnect(conn); + } else if (strcmp(type, "result") == 0) { + /* auth succeeded */ + xmpp_debug(conn->ctx, "xmpp", "Legacy auth succeeded."); + + conn->authenticated = 1; + conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata); + } else { + xmpp_error(conn->ctx, "xmpp", "Server sent us a legacy authentication "\ + "response with a bad type."); + xmpp_disconnect(conn); + } + + return 0; +} + +static int _handle_missing_legacy(xmpp_conn_t * const conn, + void * const userdata) +{ + xmpp_error(conn->ctx, "xmpp", "Server did not reply to legacy "\ + "authentication request."); + xmpp_disconnect(conn); + return 0; +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..8243df1 --- /dev/null +++ b/src/common.h @@ -0,0 +1,272 @@ +/* common.h +** XMPP client library -- internal common structures +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express or +** implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#ifndef __LIBXMPP_XMPP_COMMON_H__ +#define __LIBXMPP_XMPP_COMMON_H__ + +#include +#include +#ifndef _WIN32 +#include +#endif + + +#include "xmpp.h" +#include "sock.h" +#include "hash.h" +#include "util.h" + +#include "expat.h" + +/** run-time context **/ + +typedef enum { + XMPP_LOOP_NOTSTARTED, + XMPP_LOOP_RUNNING, + XMPP_LOOP_QUIT +} xmpp_loop_status_t; + +typedef struct _xmpp_connlist_t { + xmpp_conn_t *conn; + struct _xmpp_connlist_t *next; +} xmpp_connlist_t; + +struct _xmpp_ctx_t { + const xmpp_mem_t *mem; + const xmpp_log_t *log; + + xmpp_loop_status_t loop_status; + xmpp_connlist_t *connlist; +}; + + +/* convenience functions for accessing the context */ +void *xmpp_alloc(const xmpp_ctx_t * const ctx, const size_t size); +void xmpp_free(const xmpp_ctx_t * const ctx, void *p); +void *xmpp_realloc(const xmpp_ctx_t * const ctx, void *p, + const size_t size); +char *xmpp_strdup(const xmpp_ctx_t * const ctx, const char * const s); + +void xmpp_log(const xmpp_ctx_t * const ctx, + const xmpp_log_level_t level, + const char * const area, + const char * const fmt, + va_list ap); + +/* wrappers for xmpp_log at specific levels */ +void xmpp_error(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...); +void xmpp_warn(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...); +void xmpp_info(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...); +void xmpp_debug(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...); + +/** jid */ +/* these return new strings that must be xmpp_free()'d */ +char *xmpp_jid_new(xmpp_ctx_t *ctx, const char *node, + const char *domain, + const char *resource); +char *xmpp_jid_bare(xmpp_ctx_t *ctx, const char *jid); +char *xmpp_jid_node(xmpp_ctx_t *ctx, const char *jid); +char *xmpp_jid_domain(xmpp_ctx_t *ctx, const char *jid); +char *xmpp_jid_resource(xmpp_ctx_t *ctx, const char *jid); + + +/** connection **/ + +/* opaque connection object */ +typedef enum { + XMPP_STATE_DISCONNECTED, + XMPP_STATE_CONNECTING, + XMPP_STATE_CONNECTED +} xmpp_conn_state_t; + +typedef struct _xmpp_send_queue_t xmpp_send_queue_t; +struct _xmpp_send_queue_t { + char *data; + size_t len; + size_t written; + + xmpp_send_queue_t *next; +}; + +typedef struct _xmpp_handlist_t xmpp_handlist_t; +struct _xmpp_handlist_t { + /* common members */ + int user_handler; + void *handler; + void *userdata; + int enabled; /* handlers are added disabled and enabled after the + * handler chain is processed to prevent stanzas from + * getting processed by newly added handlers */ + xmpp_handlist_t *next; + + union { + /* timed handlers */ + struct { + unsigned long period; + uint64_t last_stamp; + }; + /* id handlers */ + struct { + char *id; + }; + /* normal handlers */ + struct { + char *ns; + char *name; + char *type; + }; + }; +}; + +#define SASL_MASK_PLAIN 0x01 +#define SASL_MASK_DIGESTMD5 0x02 +#define SASL_MASK_ANONYMOUS 0x04 + +typedef void (*xmpp_open_handler)(xmpp_conn_t * const conn); + +struct _xmpp_conn_t { + unsigned int ref; + xmpp_ctx_t *ctx; + xmpp_conn_type_t type; + + xmpp_conn_state_t state; + int error; + xmpp_stream_error_t *stream_error; + sock_t sock; + + int tls_support; + int sasl_support; /* if true, field is a bitfield of supported + mechanisms */ + + char *lang; + char *domain; + char *jid; + char *pass; + char *stream_id; + + /* send queue and parameters */ + int blocking_send; + int send_queue_max; + int send_queue_len; + xmpp_send_queue_t *send_queue_head; + xmpp_send_queue_t *send_queue_tail; + + /* xml parser */ + int reset_parser; + XML_Parser parser; + int depth; + xmpp_stanza_t *stanza; + + /* event handlers */ + + /* stream open handler */ + xmpp_open_handler open_handler; + + /* user handlers only get called after authentication */ + int authenticated; + + /* connection events handler */ + xmpp_conn_handler conn_handler; + void *userdata; + + /* other handlers */ + xmpp_handlist_t *timed_handlers; + hash_t *id_handlers; + xmpp_handlist_t *handlers; +}; + +void conn_disconnect(xmpp_conn_t * const conn); +void conn_disconnect_clean(xmpp_conn_t * const conn); +void conn_open_stream(xmpp_conn_t * const conn); + +void xmpp_send_raw_string(xmpp_conn_t * const conn, + const char * const fmt, ...); +void xmpp_send_raw(xmpp_conn_t * const conn, + const char * const data, const size_t len); + + +typedef enum { + XMPP_STANZA_UNKNOWN, + XMPP_STANZA_TEXT, + XMPP_STANZA_TAG +} xmpp_stanza_type_t; + +struct _xmpp_stanza_t { + int ref; + xmpp_ctx_t *ctx; + + xmpp_stanza_type_t type; + + xmpp_stanza_t *prev; + xmpp_stanza_t *next; + xmpp_stanza_t *children; + xmpp_stanza_t *parent; + + char *data; + + hash_t *attributes; +}; + +void xmpp_stanza_set_attributes(xmpp_stanza_t * const stanza, + const char * const * const attr); + +/* parser functions */ +void parser_handle_start(void *userdata, + const XML_Char *name, + const XML_Char **attr); +void parser_handle_character(void *userdata, const XML_Char *s, int len); +void parser_handle_end(void *userdata, const XML_Char *name); +void parser_prepare_reset(xmpp_conn_t * const conn, + xmpp_open_handler handler); +int parser_reset(xmpp_conn_t * const conn); + +/* handler management */ +void handler_fire_stanza(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza); +uint64_t handler_fire_timed(xmpp_ctx_t * const ctx); +void handler_reset_timed(xmpp_conn_t *conn, int user_only); +void handler_add_timed(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata); +void handler_add_id(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata); +void handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata); + +/* utility functions */ +void disconnect_mem_error(xmpp_conn_t * const conn); + +/* auth functions */ +void auth_handle_open(xmpp_conn_t * const conn); + +#endif /* __LIBXMPP_XMPP_COMMON_H__ */ diff --git a/src/conn.c b/src/conn.c new file mode 100644 index 0000000..0d76923 --- /dev/null +++ b/src/conn.c @@ -0,0 +1,398 @@ +/* conn.c +** connection object functions +** +** Copyright 2005 OGG, LLC +*/ + +#include +#include +#include + +#include "xmpp.h" +#include "common.h" + +#define DEFAULT_SEND_QUEUE_MAX 64 +#define DISCONNECT_TIMEOUT 2000 /* 2 seconds */ + +static int _disconnect_cleanup(xmpp_conn_t * const conn, + void * const userdata); + + +xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t * const ctx) +{ + xmpp_conn_t *conn = NULL; + xmpp_connlist_t *tail, *item; + + if (ctx == NULL) return NULL; + conn = xmpp_alloc(ctx, sizeof(xmpp_conn_t)); + + if (conn != NULL) { + conn->ctx = ctx; + + conn->type = XMPP_UNKNOWN; + conn->sock = -1; + conn->error = 0; + conn->stream_error = NULL; + + /* default send parameters */ + conn->blocking_send = 0; + conn->send_queue_max = DEFAULT_SEND_QUEUE_MAX; + conn->send_queue_len = 0; + conn->send_queue_head = NULL; + conn->send_queue_tail = NULL; + + conn->lang = xmpp_strdup(conn->ctx, "en"); + if (!conn->lang) { + xmpp_free(conn->ctx, conn); + return NULL; + } + conn->domain = NULL; + conn->jid = NULL; + conn->pass = NULL; + conn->stream_id = NULL; + + conn->tls_support = 0; + conn->sasl_support = 0; + + conn->parser = NULL; + conn->stanza = NULL; + parser_prepare_reset(conn, auth_handle_open); + + conn->authenticated = 0; + conn->conn_handler = NULL; + conn->userdata = NULL; + conn->timed_handlers = NULL; + /* we own (and will free) the hash values */ + conn->id_handlers = hash_new(conn->ctx, 32, NULL); + conn->handlers = NULL; + + /* give the caller a reference to connection */ + conn->ref = 1; + + /* add connection to ctx->connlist */ + tail = conn->ctx->connlist; + while (tail && tail->next) tail = tail->next; + + item = xmpp_alloc(conn->ctx, sizeof(xmpp_connlist_t)); + if (!item) { + xmpp_error(conn->ctx, "xmpp", "failed to allocate memory"); + xmpp_free(conn->ctx, conn->lang); + XML_ParserFree(conn->parser); + xmpp_free(conn->ctx, conn); + conn = NULL; + } else { + item->conn = conn; + item->next = NULL; + + if (tail) tail->next = item; + else conn->ctx->connlist = item; + } + } + + return conn; +} + +xmpp_conn_t * xmpp_conn_clone(xmpp_conn_t * const conn) +{ + conn->ref++; + return conn; +} + +void xmpp_conn_release(xmpp_conn_t * const conn) +{ + xmpp_ctx_t *ctx; + xmpp_connlist_t *item, *prev; + xmpp_handlist_t *hlitem, *thli; + hash_iterator_t *iter; + const char *key; + + if (conn->ref > 1) + conn->ref--; + else { + ctx = conn->ctx; + + /* remove connection from context's connlist */ + if (ctx->connlist->conn == conn) { + item = ctx->connlist; + ctx->connlist = item->next; + xmpp_free(ctx, item); + } else { + prev = NULL; + item = ctx->connlist; + while (item && item->conn != conn) { + prev = item; + item = item->next; + } + + if (!item) { + xmpp_error(ctx, "xmpp", "Connection not in context's list\n"); + } else { + prev->next = item->next; + xmpp_free(ctx, item); + } + } + + /* free handler stuff + * note that userdata is the responsibility of the client + * and the handler pointers don't need to be freed since they + * are pointers to functions */ + + hlitem = conn->timed_handlers; + while (hlitem) { + thli = hlitem; + hlitem = hlitem->next; + + xmpp_free(ctx, thli); + } + + /* id handlers + * we have to traverse the hash table freeing list elements + * then release the hash table */ + iter = hash_iter_new(conn->id_handlers); + while ((key = hash_iter_next(iter))) { + hlitem = (xmpp_handlist_t *)hash_get(conn->id_handlers, key); + while (hlitem) { + thli = hlitem; + hlitem = hlitem->next; + xmpp_free(conn->ctx, thli->id); + xmpp_free(conn->ctx, thli); + } + } + hash_iter_release(iter); + hash_release(conn->id_handlers); + + hlitem = conn->handlers; + while (hlitem) { + thli = hlitem; + hlitem = hlitem->next; + + if (hlitem->ns) xmpp_free(ctx, hlitem->ns); + if (hlitem->name) xmpp_free(ctx, hlitem->name); + if (hlitem->type) xmpp_free(ctx, hlitem->type); + xmpp_free(ctx, thli); + } + + if (conn->stream_error) { + xmpp_stanza_release(conn->stream_error->stanza); + xmpp_free(ctx, conn->stream_error); + } + + XML_ParserFree(conn->parser); + + if (conn->domain) xmpp_free(ctx, conn->domain); + if (conn->jid) xmpp_free(ctx, conn->jid); + if (conn->pass) xmpp_free(ctx, conn->pass); + if (conn->stream_id) xmpp_free(ctx, conn->stream_id); + xmpp_free(ctx, conn); + } +} + +const char *xmpp_conn_get_jid(const xmpp_conn_t * const conn) +{ + return conn->jid; +} + +/* set the jid of the user or the component name. in the first case, + * this can be a full jid, or a bare jid. in the second case, this will + * probably be a domain only. + */ +void xmpp_conn_set_jid(xmpp_conn_t * const conn, const char * const jid) +{ + if (conn->jid) xmpp_free(conn->ctx, conn->jid); + conn->jid = xmpp_strdup(conn->ctx, jid); +} + +const char *xmpp_conn_get_pass(const xmpp_conn_t * const conn) +{ + return conn->pass; +} + +void xmpp_conn_set_pass(xmpp_conn_t * const conn, const char * const pass) +{ + if (conn->pass) xmpp_free(conn->ctx, conn->pass); + conn->pass = xmpp_strdup(conn->ctx, pass); +} + +int xmpp_connect_client(xmpp_conn_t * const conn, + const char * const domain, + xmpp_conn_handler callback, + void * const userdata) +{ + conn->type = XMPP_CLIENT; + + conn->domain = xmpp_strdup(conn->ctx, domain); + if (!conn->domain) return -1; + + conn->sock = sock_connect(domain, 5222); + if (conn->sock < 0) return -1; + + /* setup handler */ + conn->conn_handler = callback; + conn->userdata = userdata; + + /* FIXME: it could happen that the connect returns immediately as + * successful, though this is pretty unlikely. This would be a little + * hard to fix, since we'd have to detect and fire off the callback + * from within the event loop */ + + conn->state = XMPP_STATE_CONNECTING; + xmpp_debug(conn->ctx, "xmpp", "attempting to connect to %s", domain); + + return 0; +} + +/* this function is only called by the end tag handler. it is + * the only place where a conn_disconnect would be called during a clean + * disconnect sequence */ +void conn_disconnect_clean(xmpp_conn_t * const conn) +{ + /* remove the timed handler */ + xmpp_timed_handler_delete(conn, _disconnect_cleanup); + + conn_disconnect(conn); +} + +void conn_disconnect(xmpp_conn_t * const conn) +{ + xmpp_info(conn->ctx, "xmpp", "Disconnected from server"); + conn->state = XMPP_STATE_DISCONNECTED; + sock_close(conn->sock); + + /* fire off connection handler */ + conn->conn_handler(conn, XMPP_CONN_DISCONNECT, conn->error, + conn->stream_error, conn->userdata); +} + +/* timed handler for cleanup if normal disconnect procedure takes too long */ +static int _disconnect_cleanup(xmpp_conn_t * const conn, + void * const userdata) +{ + xmpp_debug(conn->ctx, "xmpp", + "disconnection forced by cleanup timeout"); + + conn_disconnect(conn); + + return 0; +} + +/* terminates the XMPP stream, closing the underlying socket, + * and calls the conn_handler. this function returns immediately + * without calling the handler if the connection is not active */ +void xmpp_disconnect(xmpp_conn_t * const conn) +{ + if (conn->state != XMPP_STATE_CONNECTING && + conn->state != XMPP_STATE_CONNECTED) + return; + + /* close the stream */ + xmpp_send_raw_string(conn, ""); + + /* setup timed handler in case disconnect takes too long */ + handler_add_timed(conn, _disconnect_cleanup, + DISCONNECT_TIMEOUT, NULL); +} + +/* convinience function for sending data to a connection */ +void xmpp_send_raw_string(xmpp_conn_t * const conn, + const char * const fmt, ...) +{ + va_list ap; + size_t len; + char buf[1024]; /* small buffer for common case */ + char *bigbuf; + + va_start(ap, fmt); + len = vsnprintf(buf, 1024, fmt, ap); + va_end(ap); + + if (len >= 1024) { + /* we need more space for this data, so we allocate a big + * enough buffer and print to that */ + len++; /* account for trailing \0 */ + bigbuf = xmpp_alloc(conn->ctx, len); + if (!bigbuf) { + xmpp_debug(conn->ctx, "xmpp", "Could not allocate memory for send_raw_string"); + return; + } + va_start(ap, fmt); + vsnprintf(bigbuf, len, fmt, ap); + va_end(ap); + + xmpp_debug(conn->ctx, "conn", "SENT: %s", bigbuf); + + /* len - 1 so we don't send trailing \0 */ + xmpp_send_raw(conn, bigbuf, len - 1); + + xmpp_free(conn->ctx, bigbuf); + } else { + xmpp_debug(conn->ctx, "conn", "SENT: %s", buf); + + xmpp_send_raw(conn, buf, len); + } +} + +/* adds data to the send queue for a connection */ +void xmpp_send_raw(xmpp_conn_t * const conn, + const char * const data, const size_t len) +{ + xmpp_send_queue_t *item; + + if (conn->state != XMPP_STATE_CONNECTED) return; + + /* create send queue item for queue */ + item = xmpp_alloc(conn->ctx, sizeof(xmpp_send_queue_t)); + if (!item) return; + + item->data = xmpp_alloc(conn->ctx, len); + if (!item->data) { + xmpp_free(conn->ctx, item); + return; + } + memcpy(item->data, data, len); + item->len = len; + item->next = NULL; + item->written = 0; + + /* add item to the send queue */ + if (!conn->send_queue_tail) { + /* first item, set head and tail */ + conn->send_queue_head = item; + conn->send_queue_tail = item; + } else { + /* add to the tail */ + conn->send_queue_tail->next = item; + conn->send_queue_tail = item; + } + conn->send_queue_len++; +} + +void xmpp_send(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza) +{ + char *buf; + size_t len; + int ret; + + if (conn->state == XMPP_STATE_CONNECTED) { + if ((ret = xmpp_stanza_to_text(stanza, &buf, &len)) == 0) { + xmpp_send_raw(conn, buf, len); + xmpp_debug(conn->ctx, "xmpp", "SENT: %s", buf); + xmpp_free(conn->ctx, buf); + } + } +} + +void conn_open_stream(xmpp_conn_t * const conn) +{ + xmpp_send_raw_string(conn, + "" \ + "", + conn->domain, + conn->lang, + conn->type == XMPP_CLIENT ? XMPP_NS_CLIENT : XMPP_NS_COMPONENT, + XMPP_NS_STREAMS); +} diff --git a/src/ctx.c b/src/ctx.c new file mode 100644 index 0000000..9797bec --- /dev/null +++ b/src/ctx.c @@ -0,0 +1,187 @@ +/* ctx.c +** XMPP client library -- run-time context implementation +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include +#include +#include + +#include "xmpp.h" +#include "common.h" +#include "util.h" + +/** version **/ + +/* TODO: update from the build system? */ +#ifndef LIBXMPP_VERSION_MAJOR +#define LIBXMPP_VERSION_MAJOR (0) +#endif +#ifndef LIBXMPP_VERSION_MINOR +#define LIBXMPP_VERSION_MINOR (0) +#endif + +int xmpp_version_check(int major, int minor) +{ + return (major == LIBXMPP_VERSION_MAJOR) && + (minor >= LIBXMPP_VERSION_MINOR); +} + +/** run-time contexts **/ + +/* define the global default allocator, logger and context here */ + +static xmpp_mem_t xmpp_default_mem = { + malloc, /* use the stdlib routines by default */ + free, + realloc +}; + +static const char * const xmpp_log_level_name[4] = {"DEBUG", "INFO", "WARN", "ERROR"}; + +void xmpp_default_logger(void * const userdata, + const xmpp_log_level_t level, + const char * const area, + const char * const msg) +{ + fprintf(stderr, "%s %s %s\n", area, xmpp_log_level_name[level], msg); +} + +#ifdef _WIN32 +static xmpp_log_t xmpp_default_log = { NULL, NULL }; +#else +static xmpp_log_t xmpp_default_log = { xmpp_default_logger, NULL }; +#endif + + +/** convenience functions for accessing the context **/ + +/* allocator */ + +void *xmpp_alloc(const xmpp_ctx_t * const ctx, const size_t size) +{ + return ctx->mem->alloc(size); +} + +void xmpp_free(const xmpp_ctx_t * const ctx, void *p) +{ + return ctx->mem->free(p); +} + +void *xmpp_realloc(const xmpp_ctx_t * const ctx, void *p, + const size_t size) +{ + return ctx->mem->realloc(p, size); +} + +/* logger */ + +void xmpp_log(const xmpp_ctx_t * const ctx, + const xmpp_log_level_t level, + const char * const area, + const char * const fmt, + va_list ap) +{ + char buf[1024]; + + /* FIXME: we don't send log lines > 1024 chars */ + + vsnprintf(buf, 1023, fmt, ap); + + ctx->log->handler(ctx->log->userdata, level, area, buf); +} + +void xmpp_error(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + xmpp_log(ctx, XMPP_LEVEL_ERROR, area, fmt, ap); + va_end(ap); +} + +void xmpp_warn(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + xmpp_log(ctx, XMPP_LEVEL_WARN, area, fmt, ap); + va_end(ap); +} + +void xmpp_info(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + xmpp_log(ctx, XMPP_LEVEL_INFO, area, fmt, ap); + va_end(ap); +} + +void xmpp_debug(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + xmpp_log(ctx, XMPP_LEVEL_DEBUG, area, fmt, ap); + va_end(ap); +} + +/** allocate and initialize a new ctx object */ +xmpp_ctx_t *xmpp_ctx_new(const xmpp_mem_t * const mem, + const xmpp_log_t * const log) +{ + xmpp_ctx_t *ctx = NULL; + + if (mem == NULL) + ctx = xmpp_default_mem.alloc(sizeof(xmpp_ctx_t)); + else + ctx = mem->alloc(sizeof(xmpp_ctx_t)); + + if (ctx != NULL) { + if (mem != NULL) + ctx->mem = mem; + else + ctx->mem = &xmpp_default_mem; + + if (log == NULL) + ctx->log = &xmpp_default_log; + else + ctx->log = log; + + ctx->connlist = NULL; + ctx->loop_status = XMPP_LOOP_NOTSTARTED; + } + + return ctx; +} + +/** free a ctx object no longer in use */ +void xmpp_ctx_free(xmpp_ctx_t * const ctx) +{ + /* mem and log are owned by their suppliers */ + xmpp_free(ctx, ctx); /* pull the hole in after us */ +} + diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..005ff8b --- /dev/null +++ b/src/event.c @@ -0,0 +1,220 @@ +/* event.c +** XMPP client library -- event loop and management +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include +#include + +#include + +#include "xmpp.h" +#include "common.h" + +#define DEFAULT_TIMEOUT 1 + +/* send data and check all connections for their events + * and call event handlers. timeout is in milliseconds */ +void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) +{ + xmpp_connlist_t *connitem; + xmpp_conn_t *conn; + fd_set rfds, wfds; + sock_t max = 0; + int ret; + struct timeval tv; + xmpp_send_queue_t *sq, *tsq; + size_t towrite; + char buf[4096]; + uint64_t next; + + if (ctx->loop_status == XMPP_LOOP_QUIT) return; + ctx->loop_status = XMPP_LOOP_RUNNING; + + /* send queued data */ + connitem = ctx->connlist; + while (connitem) { + conn = connitem->conn; + if (conn->state != XMPP_STATE_CONNECTED) { + connitem = connitem->next; + continue; + } + + sq = conn->send_queue_head; + while (sq) { + towrite = sq->len - sq->written; + ret = sock_write(conn->sock, &sq->data[sq->written], towrite); + + if (ret < 0 && sock_is_recoverable(sock_error())) { + /* an error occured */ + conn->error = sock_error(); + break; + } else if (ret < towrite) { + /* not all data could be sent now */ + sq->written += ret; + break; + } + + /* all data for this queue item written, delete and move on */ + xmpp_free(ctx, sq->data); + tsq = sq; + sq = sq->next; + xmpp_free(ctx, tsq); + + /* pop the top item */ + conn->send_queue_head = sq; + /* if we've sent everything update the tail */ + if (!sq) conn->send_queue_tail = NULL; + } + + /* tear down connection on error */ + if (conn->error) { + /* FIXME: need to tear down send queues and random other things + * maybe this should be abstracted */ + xmpp_debug(ctx, "xmpp", "send error occured, tear down socket"); + sock_close(conn->sock); + conn->state = XMPP_STATE_DISCONNECTED; + } + + connitem = connitem->next; + } + + /* reset parsers if needed */ + for (connitem = ctx->connlist; connitem; connitem = connitem->next) { + if (connitem->conn->reset_parser) + parser_reset(connitem->conn); + } + + + /* fire any ready timed handlers, then + make sure we don't wait past the time when timed handlers need + to be called */ + next = handler_fire_timed(ctx); + + tv.tv_sec = 0; + tv.tv_usec = ((next < timeout) ? next : timeout) * 1000; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + /* find events to watch */ + connitem = ctx->connlist; + while (connitem) { + conn = connitem->conn; + + switch (conn->state) { + case XMPP_STATE_CONNECTING: + /* connect has been called and we're waiting for it to complete */ + /* connection will give us write or error events */ + FD_SET(conn->sock, &wfds); + break; + case XMPP_STATE_CONNECTED: + FD_SET(conn->sock, &rfds); + break; + case XMPP_STATE_DISCONNECTED: + /* do nothing */ + default: + break; + } + + if (conn->sock > max) max = conn->sock; + + connitem = connitem->next; + } + + /* check for events */ + ret = select(max + 1, &rfds, &wfds, NULL, &tv); + + /* select errored */ + if (ret < 0) { + xmpp_error(ctx, "xmpp", "event watcher internal error"); + return; + } + + /* no events happened */ + if (ret == 0) return; + + /* process events */ + connitem = ctx->connlist; + while (connitem) { + conn = connitem->conn; + + switch (conn->state) { + case XMPP_STATE_CONNECTING: + if (FD_ISSET(conn->sock, &wfds)) { + /* connection complete */ + + /* check for error */ + if (sock_connect_error(conn->sock) != 0) { + /* connection failed */ + xmpp_debug(ctx, "xmpp", "connection failed"); + conn->state = XMPP_STATE_DISCONNECTED; + sock_close(conn->sock); + break; + } + + conn->state = XMPP_STATE_CONNECTED; + xmpp_debug(ctx, "xmpp", "connection successful"); + + + /* send stream init */ + conn_open_stream(conn); + } + + break; + case XMPP_STATE_CONNECTED: + if (FD_ISSET(conn->sock, &rfds)) { + ret = sock_read(conn->sock, buf, 4096); + if (ret > 0) { + ret = XML_Parse(conn->parser, buf, ret, 0); + if (!ret) { + /* parse error, we need to shut down */ + /* FIXME */ + } + } + /* return of 0 means socket closed by server */ + } + + break; + case XMPP_STATE_DISCONNECTED: + /* do nothing */ + default: + break; + } + + connitem = connitem->next; + } + + /* fire any ready handlers */ + handler_fire_timed(ctx); +} + +void xmpp_run(xmpp_ctx_t *ctx) +{ + if (ctx->loop_status != XMPP_LOOP_NOTSTARTED) return; + + ctx->loop_status = XMPP_LOOP_RUNNING; + while (ctx->loop_status == XMPP_LOOP_RUNNING) { + xmpp_run_once(ctx, DEFAULT_TIMEOUT); + } + + xmpp_debug(ctx, "event", "Event loop completed."); +} + +void xmpp_stop(xmpp_ctx_t *ctx) +{ + xmpp_debug(ctx, "event", "Stopping event loop."); + + if (ctx->loop_status == XMPP_LOOP_RUNNING) + ctx->loop_status = XMPP_LOOP_QUIT; +} diff --git a/src/handler.c b/src/handler.c new file mode 100644 index 0000000..ef54dc7 --- /dev/null +++ b/src/handler.c @@ -0,0 +1,446 @@ +/* handler.c +** XMPP client library -- event handler management +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include +#include +#include + +#include "xmpp.h" +#include "common.h" + +void handler_fire_stanza(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza) +{ + xmpp_handlist_t *item, *prev; + char *id, *ns, *name, *type; + + /* call id handlers */ + id = xmpp_stanza_get_id(stanza); + if (id) { + item = (xmpp_handlist_t *)hash_get(conn->id_handlers, id); + for (; item; item = item->next) { + if (item->user_handler && !conn->authenticated) + continue; + + ((xmpp_handler)(item->handler))(conn, stanza, item->userdata); + } + } + + /* call handlers */ + ns = xmpp_stanza_get_ns(stanza); + name = xmpp_stanza_get_name(stanza); + type = xmpp_stanza_get_type(stanza); + + /* enable all added handlers */ + for (item = conn->handlers; item; item = item->next) + item->enabled = 1; + + prev = NULL; + item = conn->handlers; + while (item) { + /* skip newly added handlers */ + if (!item->enabled) { + item = item->next; + continue; + } + + /* don't call user handlers until authentication succeeds */ + if (item->user_handler && !conn->authenticated) { + item = item->next; + continue; + } + + if ((!item->ns || (ns && strcmp(ns, item->ns) == 0)) && + (!item->name || (name && strcmp(name, item->name) == 0)) && + (!item->type || (type && strcmp(type, item->type) == 0))) + if (!((xmpp_handler)(item->handler))(conn, stanza, item->userdata)) { + /* handler is one-shot, so delete it */ + if (prev) + prev->next = item->next; + else + conn->handlers = item->next; + xmpp_free(conn->ctx, item); + item = NULL; + } + + if (item) { + prev = item; + item = item->next; + } else if (prev) + item = prev->next; + else + item = conn->handlers; + } +} + +/* helper function to fire timed handlers. returns the + * time until the next handler would be fired */ +uint64_t handler_fire_timed(xmpp_ctx_t * const ctx) +{ + xmpp_connlist_t *connitem; + xmpp_handlist_t *handitem, *temp; + int ret, fired; + uint64_t elapsed, min; + + min = (uint64_t)(-1); + + connitem = ctx->connlist; + while (connitem) { + if (connitem->conn->state != XMPP_STATE_CONNECTED) { + connitem = connitem->next; + continue; + } + + /* enable all handlers that were added */ + for (handitem = connitem->conn->timed_handlers; handitem; + handitem = handitem->next) + handitem->enabled = 1; + + handitem = connitem->conn->timed_handlers; + while (handitem) { + /* skip newly added handlers */ + if (!handitem->enabled) { + handitem = handitem->next; + continue; + } + + /* only fire user handlers after authentication */ + if (handitem->user_handler && !connitem->conn->authenticated) { + handitem = handitem->next; + continue; + } + + fired = 0; + elapsed = time_elapsed(handitem->last_stamp, time_stamp()); + if (elapsed >= handitem->period) { + /* fire! */ + fired = 1; + handitem->last_stamp = time_stamp(); + ret = ((xmpp_timed_handler)handitem->handler)(connitem->conn, handitem->userdata); + } else if (min > (handitem->period - elapsed)) + min = handitem->period - elapsed; + + temp = handitem; + handitem = handitem->next; + + /* delete handler if it returned false */ + if (fired && !ret) + xmpp_timed_handler_delete(connitem->conn, temp->handler); + } + + connitem = connitem->next; + } + + return min; +} + +/* reset timed handlers for a connection. this is called + * whenever connection is successful */ +void handler_reset_timed(xmpp_conn_t *conn, int user_only) +{ + xmpp_handlist_t *handitem; + + handitem = conn->timed_handlers; + while (handitem) { + if ((user_only && handitem->user_handler) || !user_only) + handitem->last_stamp = time_stamp(); + + handitem = handitem->next; + } +} + +static void _timed_handler_add(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata, + const int user_handler) +{ + xmpp_handlist_t *item, *tail; + + /* check if handler is already in the list */ + for (item = conn->timed_handlers; item; item = item->next) { + if (item->handler == (void *)handler) + break; + } + if (item) return; + + /* build new item */ + item = xmpp_alloc(conn->ctx, sizeof(xmpp_handlist_t)); + if (!item) return; + + item->user_handler = user_handler; + item->handler = (void *)handler; + item->userdata = userdata; + item->enabled = 0; + item->next = NULL; + + item->period = period; + item->last_stamp = time_stamp(); + + /* append item to list */ + if (!conn->timed_handlers) + conn->timed_handlers = item; + else { + tail = conn->timed_handlers; + while (tail->next) + tail = tail->next; + tail->next = item; + } +} + +void xmpp_timed_handler_delete(xmpp_conn_t * const conn, + xmpp_timed_handler handler) +{ + xmpp_handlist_t *item, *prev; + + if (!conn->timed_handlers) return; + + prev = NULL; + item = conn->timed_handlers; + while (item) { + if (item->handler == (void *)handler) + break; + prev = item; + item = item->next; + } + + if (item) { + if (prev) + prev->next = item->next; + else + conn->timed_handlers = item->next; + + xmpp_free(conn->ctx, item); + } +} + +static void _id_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata, int user_handler) +{ + xmpp_handlist_t *item, *tail; + + /* check if handler is already in the list */ + item = (xmpp_handlist_t *)hash_get(conn->id_handlers, id); + while (item) { + if (item->handler == (void *)handler) + break; + item = item->next; + } + if (item) return; + + /* build new item */ + item = xmpp_alloc(conn->ctx, sizeof(xmpp_handlist_t)); + if (!item) return; + + item->user_handler = user_handler; + item->handler = (void *)handler; + item->userdata = userdata; + item->enabled = 0; + item->next = NULL; + + item->id = xmpp_strdup(conn->ctx, id); + if (!item->id) { + xmpp_free(conn->ctx, item); + return; + } + + /* put on list in hash table */ + tail = (xmpp_handlist_t *)hash_get(conn->id_handlers, id); + if (!tail) + hash_add(conn->id_handlers, id, item); + else { + while (tail->next) + tail = tail->next; + tail->next = item; + } +} + +void xmpp_id_handler_delete(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id) +{ + xmpp_handlist_t *item, *prev; + + prev = NULL; + item = (xmpp_handlist_t *)hash_get(conn->id_handlers, id); + if (!item) return; + + while (item) { + if (item->handler == (void *)handler) + break; + + prev = item; + item = item->next; + } + + if (item) { + if (prev) + prev->next = item->next; + else { + hash_drop(conn->id_handlers, id); + hash_add(conn->id_handlers, id, item->next); + } + xmpp_free(conn->ctx, item->id); + xmpp_free(conn->ctx, item); + } +} + +static void _handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata, int user_handler) +{ + xmpp_handlist_t *item, *tail; + + /* check if handler already in list */ + for (item = conn->handlers; item; item = item->next) { + if (item->handler == (void *)handler) + break; + } + if (item) return; + + /* build new item */ + item = (xmpp_handlist_t *)xmpp_alloc(conn->ctx, sizeof(xmpp_handlist_t)); + if (!item) return; + + item->user_handler = user_handler; + item->handler = (void *)handler; + item->userdata = userdata; + item->enabled = 0; + item->next = NULL; + + if (ns) { + item->ns = xmpp_strdup(conn->ctx, ns); + if (!item->ns) { + xmpp_free(conn->ctx, item); + return; + } + } else + item->ns = NULL; + if (name) { + item->name = xmpp_strdup(conn->ctx, name); + if (!item->name) { + if (item->ns) xmpp_free(conn->ctx, item->ns); + xmpp_free(conn->ctx, item); + return; + } + } else + item->name = NULL; + if (type) { + item->type = xmpp_strdup(conn->ctx, type); + if (!item->type) { + if (item->ns) xmpp_free(conn->ctx, item->ns); + if (item->name) xmpp_free(conn->ctx, item->name); + xmpp_free(conn->ctx, item); + } + } else + item->type = NULL; + + /* append to list */ + if (!conn->handlers) + conn->handlers = item; + else { + tail = conn->handlers; + while (tail->next) + tail = tail->next; + tail->next = item; + } +} + +void xmpp_handler_delete(xmpp_conn_t * const conn, + xmpp_handler handler) +{ + xmpp_handlist_t *prev, *item; + + if (!conn->handlers) return; + + prev = NULL; + item = conn->handlers; + while (item) { + if (item->handler == (void *)handler) + break; + + prev = item; + item = item->next; + } + + if (item) { + if (prev) + prev->next = item->next; + else + conn->handlers = item->next; + + if (item->ns) xmpp_free(conn->ctx, item->ns); + if (item->name) xmpp_free(conn->ctx, item->name); + if (item->type) xmpp_free(conn->ctx, item->type); + xmpp_free(conn->ctx, item); + } +} + +void xmpp_timed_handler_add(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata) +{ + _timed_handler_add(conn, handler, period, userdata, 1); +} + +void handler_add_timed(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata) +{ + _timed_handler_add(conn, handler, period, userdata, 0); +} + +void xmpp_id_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata) +{ + _id_handler_add(conn, handler, id, userdata, 1); +} + +void handler_add_id(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata) +{ + _id_handler_add(conn, handler, id, userdata, 0); +} + +void xmpp_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata) +{ + _handler_add(conn, handler, ns, name, type, userdata, 1); +} + +void handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata) +{ + _handler_add(conn, handler, ns, name, type, userdata, 0); +} diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 0000000..b67dc93 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,272 @@ +/* hash.c +** XMPP client library +** hash table implementation +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include + +#include "xmpp.h" +#include "common.h" +#include "hash.h" + +/* private types */ +typedef struct _hashentry_t hashentry_t; + +struct _hashentry_t { + hashentry_t *next; + char *key; + void *value; +}; + +struct _hash_t { + unsigned int ref; + xmpp_ctx_t *ctx; + hash_free_func free; + int length; + int num_keys; + hashentry_t **entries; +}; + +struct _hash_iterator_t { + unsigned int ref; + hash_t *table; + hashentry_t *entry; + int index; +}; + +/** allocate and initialize a new hash table */ +hash_t *hash_new(xmpp_ctx_t * const ctx, const int size, + hash_free_func free) +{ + hash_t *result = NULL; + + result = xmpp_alloc(ctx, sizeof(hash_t)); + if (result != NULL) { + result->entries = xmpp_alloc(ctx, size * sizeof(hashentry_t *)); + if (result->entries == NULL) { + xmpp_free(ctx, result); + return NULL; + } + memset(result->entries, 0, size * sizeof(hashentry_t *)); + result->length = size; + + result->ctx = ctx; + result->free = free; + result->num_keys = 0; + /* give the caller a reference */ + result->ref = 1; + } + + return result; +} + +/** obtain a new reference to an existing hash table */ +hash_t *hash_clone(hash_t * const table) +{ + table->ref++; + return table; +} + +/** release a hash table that is no longer needed */ +void hash_release(hash_t * const table) +{ + xmpp_ctx_t *ctx = table->ctx; + hashentry_t *entry, *next; + int i; + + if (table->ref > 1) + table->ref--; + else { + for (i = 0; i < table->length; i++) { + entry = table->entries[i]; + while (entry != NULL) { + next = entry->next; + xmpp_free(ctx, entry->key); + if (table->free) table->free(ctx, entry->value); + xmpp_free(ctx, entry); + entry = next; + } + } + xmpp_free(ctx, table->entries); + xmpp_free(ctx, table); + } +} + +/** hash a key for our table lookup */ +static int _hash_key(hash_t *table, const char *key) +{ + int hash = 0; + int shift = 0; + const char *c = key; + + while (*c != '\0') { + /* assume 32 bit ints */ + hash ^= ((int)*c++ << shift); + shift += 8; + if (shift > 24) shift = 0; + } + + return hash % table->length; +} + +/** add a key, value pair to a hash table. + * each key can appear only once; the value of any + * identical key will be replaced + */ +int hash_add(hash_t *table, const char * const key, void *data) +{ + xmpp_ctx_t *ctx = table->ctx; + hashentry_t *entry = NULL; + int index = _hash_key(table, key); + + /* allocate and fill a new entry */ + entry = xmpp_alloc(ctx, sizeof(hashentry_t)); + if (!entry) return -1; + entry->key = xmpp_strdup(ctx, key); + if (!entry->key) { + xmpp_free(ctx, entry); + return -1; + } + entry->value = data; + /* insert ourselves in the linked list */ + /* TODO: this leaks duplicate keys */ + entry->next = table->entries[index]; + table->entries[index] = entry; + table->num_keys++; + + return 0; +} + +/** look up a key in a hash table */ +void *hash_get(hash_t *table, const char *key) +{ + hashentry_t *entry; + int index = _hash_key(table, key); + void *result = NULL; + + /* look up the hash entry */ + entry = table->entries[index]; + while (entry != NULL) { + /* traverse the linked list looking for the key */ + if (!strcmp(key, entry->key)) { + /* match */ + result = entry->value; + return result; + } + entry = entry->next; + } + /* no match */ + return result; +} + +/** delete a key from a hash table */ +int hash_drop(hash_t *table, const char *key) +{ + xmpp_ctx_t *ctx = table->ctx; + hashentry_t *entry, *prev; + int index = _hash_key(table, key); + + /* look up the hash entry */ + entry = table->entries[index]; + prev = NULL; + while (entry != NULL) { + /* traverse the linked list looking for the key */ + if (!strcmp(key, entry->key)) { + /* match, remove the entry */ + xmpp_free(ctx, entry->key); + if (table->free) table->free(ctx, entry->value); + if (prev == NULL) { + table->entries[index] = entry->next; + } else { + prev->next = entry->next; + } + xmpp_free(ctx, entry); + table->num_keys--; + return 0; + } + prev = entry; + entry = entry->next; + } + /* no match */ + return -1; +} + +int hash_num_keys(hash_t *table) +{ + return table->num_keys; +} + +/** allocate and initialize a new iterator */ +hash_iterator_t *hash_iter_new(hash_t *table) +{ + xmpp_ctx_t *ctx = table->ctx; + hash_iterator_t *iter; + + iter = xmpp_alloc(ctx, sizeof(*iter)); + if (iter != NULL) { + iter->ref = 1; + iter->table = hash_clone(table); + iter->entry = NULL; + iter->index = 0; + } + + return iter; +} + + +/** release an iterator that is no longer needed */ +void hash_iter_release(hash_iterator_t *iter) +{ + xmpp_ctx_t *ctx = iter->table->ctx; + + iter->ref--; + + if (iter->ref <= 0) { + hash_release(iter->table); + xmpp_free(ctx, iter); + } +} + +/** return the next hash table key from the iterator. + the returned key should not be freed */ +const char * hash_iter_next(hash_iterator_t *iter) +{ + hash_t *table = iter->table; + hashentry_t *entry = iter->entry; + int i = iter->index + 1; + + /* advance until we find the next entry */ + if (entry != NULL) entry = entry->next; + if (entry == NULL) { + /* we're off the end of list, search for a new entry */ + while (i < iter->table->length) { + entry = table->entries[i]; + if (entry != NULL) { + iter->index = i; + break; + } + i++; + } + } + + if ((entry == NULL) || (i >= table->length)) { + /* no more keys! */ + return NULL; + } + + /* remember our current match */ + iter->entry = entry; + return entry->key; +} + diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 0000000..925d34e --- /dev/null +++ b/src/hash.h @@ -0,0 +1,60 @@ +/* hash.h +** XMPP client library -- hash table interface +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#ifndef __LIBXMPP_HASH_H__ +#define __LIBXMPP_HASH_H__ + +typedef struct _hash_t hash_t; + +typedef void (*hash_free_func)(const xmpp_ctx_t * const ctx, void *p); + +/** allocate and initialize a new hash table */ +hash_t *hash_new(xmpp_ctx_t * const ctx, const int size, + hash_free_func free); + +/** allocate a new reference to an existing hash table */ +hash_t *hash_clone(hash_t * const table); + +/** release a hash table when no longer needed */ +void hash_release(hash_t * const table); + +/** add a key, value pair to a hash table. + * each key can appear only once; the value of any + * identical key will be replaced + */ +int hash_add(hash_t *table, const char * const key, void *data); + +/** look up a key in a hash table */ +void *hash_get(hash_t *table, const char *key); + +/** delete a key from a hash table */ +int hash_drop(hash_t *table, const char *key); + +/** return the number of keys in a hash */ +int hash_num_keys(hash_t *table); + +/** hash key iterator functions */ +typedef struct _hash_iterator_t hash_iterator_t; + +/** allocate and initialize a new iterator */ +hash_iterator_t *hash_iter_new(hash_t *table); + +/** release an iterator that is no longer needed */ +void hash_iter_release(hash_iterator_t *iter); + +/** return the next hash table key from the iterator. + the returned key should not be freed */ +const char * hash_iter_next(hash_iterator_t *iter); + +#endif /* __LIBXMPPP_HASH_H__ */ diff --git a/src/jid.c b/src/jid.c new file mode 100644 index 0000000..b2ea708 --- /dev/null +++ b/src/jid.c @@ -0,0 +1,139 @@ +/* jid.c +** XMPP client library +** helper functions for parsing jid's +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include + +#include "xmpp.h" +#include "common.h" + +/** join jid component parts to form a new jid string */ +char *xmpp_jid_new(xmpp_ctx_t *ctx, const char *node, + const char *domain, + const char *resource) +{ + char *result; + int len,nlen,dlen,rlen; + + /* jid must at least have a domain */ + if (domain == NULL) return NULL; + + /* accumulate lengths */ + dlen = strlen(domain); + nlen = (node) ? strlen(node) + 1 : 0; + rlen = (resource) ? strlen(resource) + 1 : 0; + len = nlen + dlen + rlen; + + /* concat components */ + result = xmpp_alloc(ctx, len + 1); + if (result != NULL) { + if (node != NULL) { + memcpy(result, node, nlen - 1); + result[nlen-1] = '@'; + } + memcpy(result + nlen, domain, dlen); + if (resource != NULL) { + result[nlen+dlen] = '/'; + memcpy(result+nlen+dlen+1, resource, rlen - 1); + } + result[nlen+dlen+rlen] = '\0'; + } + + return result; +} + +/** return a bare jid */ +char *xmpp_jid_bare(xmpp_ctx_t *ctx, const char *jid) +{ + char *result; + const char *c; + + c = strchr(jid, '/'); + if (c == NULL) return xmpp_strdup(ctx, jid); + + result = xmpp_alloc(ctx, c-jid+1); + if (result != NULL) { + memcpy(result, jid, c-jid); + result[c-jid] = '\0'; + } + + return result; +} + +/** return the node portion of a jid */ +char *xmpp_jid_node(xmpp_ctx_t *ctx, const char *jid) +{ + char *result = NULL; + const char *c; + + c = strchr(jid, '@'); + if (c != NULL) { + result = xmpp_alloc(ctx, (c-jid) + 1); + if (result != NULL) { + memcpy(result, jid, (c-jid)); + result[c-jid] = '\0'; + } + } + + return result; +} + +/** return the domain portion of a jid */ +char *xmpp_jid_domain(xmpp_ctx_t *ctx, const char *jid) +{ + char *result = NULL; + const char *c,*s; + + c = strchr(jid, '@'); + if (c == NULL) { + /* no node, assume domain */ + c = jid; + } else { + /* advance past the separator */ + c++; + } + s = strchr(c, '/'); + if (s == NULL) { + /* no resource */ + s = c + strlen(c); + } + result = xmpp_alloc(ctx, (s-c) + 1); + if (result != NULL) { + memcpy(result, c, (s-c)); + result[s-c] = '\0'; + } + + return result; +} + +/** return the node portion of a jid */ +char *xmpp_jid_resource(xmpp_ctx_t *ctx, const char *jid) +{ + char *result = NULL; + const char *c; + int len; + + c = strchr(jid, '/'); + if (c != NULL) { + c++; + len = strlen(c); + result = xmpp_alloc(ctx, len + 1); + if (result != NULL) { + memcpy(result, c, len); + result[len] = '\0'; + } + } + + return result; +} diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 0000000..a68323d --- /dev/null +++ b/src/md5.c @@ -0,0 +1,285 @@ +/* md5.c +** MD5 hash function implemention, adapted for local use +** +** This code is in the Public Domain +*/ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include /* memcpy(), memset() */ +#include "md5.h" + +/* little-endian word access macros */ +#define GET_32BIT_LSB_FIRST(cp) \ + (((uint32_t)(unsigned char)(cp)[0]) | \ + ((uint32_t)(unsigned char)(cp)[1] << 8 ) | \ + ((uint32_t)(unsigned char)(cp)[2] << 16) | \ + ((uint32_t)(unsigned char)(cp)[3] << 24)) + +#define PUT_32BIT_LSB_FIRST(cp, value) \ + do { \ + (cp)[0] = (value) & 0xFF; \ + (cp)[1] = ((value) >> 8) & 0xFF; \ + (cp)[2] = ((value) >> 16) & 0xFF; \ + (cp)[3] = ((value) >> 24) & 0xFF; \ + } while(0) + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; + + memset(ctx->in, 0, 64); +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(struct MD5Context *ctx, unsigned char const *buf, uint32_t len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = (t + ((uint32_t)len << 3)) & 0xffffffff) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + MD5Transform(ctx->buf, ctx->in, ctx); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + MD5Transform(ctx->buf, ctx->in, ctx); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + MD5Transform(ctx->buf, ctx->in, ctx); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + + /* Append length in bits and transform */ + PUT_32BIT_LSB_FIRST(ctx->in + 56, ctx->bits[0]); + PUT_32BIT_LSB_FIRST(ctx->in + 60, ctx->bits[1]); + + MD5Transform(ctx->buf, ctx->in, ctx); + PUT_32BIT_LSB_FIRST(digest, ctx->buf[0]); + PUT_32BIT_LSB_FIRST(digest + 4, ctx->buf[1]); + PUT_32BIT_LSB_FIRST(digest + 8, ctx->buf[2]); + PUT_32BIT_LSB_FIRST(digest + 12, ctx->buf[3]); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +/* debugging version: */ +/* +#define MD5STEP(f, w, x, y, z, data, s) \ + printf("MD5STEP: w: %x x: %x y: %x z: %x data: %x s: %x\n", \ + w, x, y, z, data, s); \ + printf("f(x,y,z) = %x\n", f(x,y,z)+data); \ + ( w += f(x, y, z) + data, printf(" - w: %x ", w), \ + w = w<>(32-s), printf(" - w: %x\n", w), w += x ) +*/ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Transform(uint32_t buf[4], const unsigned char inext[64], + struct MD5Context *ctx) +{ + register uint32_t a, b, c, d, i; + uint32_t in[16]; + + for (i = 0; i < 16; i++) + in[i] = GET_32BIT_LSB_FIRST(inext + 4 * i); + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#ifdef DEBUG_MD5 + +#include + +void MD5DumpBytes(unsigned char *b, int len) +{ + int i; + for (i=0; i +#endif + +struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, unsigned char const *buf, + uint32_t len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); +void MD5Transform(uint32_t buf[4], const unsigned char in[64], + struct MD5Context *ctx); + +#ifdef DEBUG_MD5 +void MD5DumpBytes(unsigned char *b, int len); +#endif + +#endif /* !MD5_H */ diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..4bf03ca --- /dev/null +++ b/src/parser.c @@ -0,0 +1,202 @@ +/* parser.c +** XMPP client library -- xml parser handlers and utility functions +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include +#include + +#include "expat.h" + +#include "xmpp.h" +#include "common.h" + +static void _log_open_tag(xmpp_conn_t * const conn, + const XML_Char **attr) +{ + char buf[4096]; + size_t len, pos; + int i; + + pos = 0; + len = snprintf(buf, 4096, ""); + if (len < 0) return; + + xmpp_debug(conn->ctx, "xmpp", "RECV: %s", buf); +} + +void parser_handle_start(void *userdata, + const XML_Char *name, + const XML_Char **attr) +{ + xmpp_conn_t *conn = (xmpp_conn_t *)userdata; + xmpp_stanza_t *child; + + if (conn->depth == 0) { + /* we're expecting a stream:stream tag. */ + if (strcmp(name, "stream:stream") != 0) { + xmpp_error(conn->ctx, "xmpp", + "Server did not open valid stream."); + conn_disconnect(conn); + } else { + _log_open_tag(conn, attr); + + if (conn->stream_id) xmpp_free(conn->ctx, conn->stream_id); + conn->stream_id = xmpp_strdup(conn->ctx, "foo"); + if (!conn->stream_id) { + xmpp_error(conn->ctx, "xmpp", + "Memory allocation failure."); + conn_disconnect(conn); + } + + /* call stream open handler */ + conn->open_handler(conn); + } + } else { + /* build stanzas at depth 1 */ + if (!conn->stanza && conn->depth != 1) { + /* something terrible happened */ + /* FIXME: shutdown disconnect */ + xmpp_debug(conn->ctx, "xmpp", "oops, where did our stanza go?"); + } else if (!conn->stanza) { + /* starting a new toplevel stanza */ + conn->stanza = xmpp_stanza_new(conn->ctx); + if (!conn->stanza) { + /* FIXME: can't allocate, disconnect */ + } + xmpp_stanza_set_name(conn->stanza, name); + xmpp_stanza_set_attributes(conn->stanza, attr); + } else { + /* starting a child of conn->stanza */ + child = xmpp_stanza_new(conn->ctx); + if (!child) { + /* FIXME: can't allocate, disconnect */ + } + xmpp_stanza_set_name(child, name); + xmpp_stanza_set_attributes(child, attr); + + /* add child to parent */ + xmpp_stanza_add_child(conn->stanza, child); + + /* the child is owned by the toplevel stanza now */ + xmpp_stanza_release(child); + + /* make child the current stanza */ + conn->stanza = child; + } + } + + conn->depth++; +} + +void parser_handle_end(void *userdata, const XML_Char *name) +{ + xmpp_conn_t *conn = (xmpp_conn_t *)userdata; + char *buf; + size_t len; + xmpp_stanza_t *stanza; + + conn->depth--; + + if (conn->depth == 0) { + /* got a closing stream tag */ + xmpp_debug(conn->ctx, "xmpp", "RECV: "); + conn_disconnect_clean(conn); + } else { + if (conn->stanza->parent) { + /* we're finishing a child stanza, so set current to the parent */ + conn->stanza = conn->stanza->parent; + } else { + /* we're finishing a toplevel stanza, so fire off handler */ + if (xmpp_stanza_to_text(conn->stanza, &buf, &len) == 0) { + xmpp_debug(conn->ctx, "xmpp", "RECV: %s", buf); + xmpp_free(conn->ctx, buf); + } + + stanza = xmpp_stanza_clone(conn->stanza); + xmpp_stanza_release(conn->stanza); + conn->stanza = NULL; + + /* fire handlers */ + handler_fire_stanza(conn, stanza); + + xmpp_stanza_release(stanza); + } + } +} + +void parser_handle_character(void *userdata, const XML_Char *s, int len) +{ + xmpp_conn_t *conn = (xmpp_conn_t *)userdata; + xmpp_stanza_t *stanza; + + if (conn->depth < 2) return; + + /* create and populate stanza */ + stanza = xmpp_stanza_new(conn->ctx); + if (!stanza) { + /* FIXME: allocation error, disconnect */ + return; + } + xmpp_stanza_set_text_with_size(stanza, s, len); + + xmpp_stanza_add_child(conn->stanza, stanza); + xmpp_stanza_release(stanza); +} + +/* prepares a parser reset. this is called from handlers. we can't + * reset the parser immediately as it is not reentrant. */ +void parser_prepare_reset(xmpp_conn_t * const conn, + xmpp_open_handler handler) +{ + conn->reset_parser = 1; + conn->open_handler = handler; +} + +/* shuts down and restarts XML parser. true on success */ +int parser_reset(xmpp_conn_t * const conn) +{ + conn->reset_parser = 0; + + if (conn->parser) + XML_ParserFree(conn->parser); + + if (conn->stanza) + xmpp_stanza_release(conn->stanza); + + conn->parser = XML_ParserCreate(NULL); + if (!conn->parser) return 0; + + conn->depth = 0; + conn->stanza = NULL; + XML_SetUserData(conn->parser, conn); + XML_SetElementHandler(conn->parser, parser_handle_start, + parser_handle_end); + XML_SetCharacterDataHandler(conn->parser, parser_handle_character); + + return 1; +} + diff --git a/src/sasl.c b/src/sasl.c new file mode 100644 index 0000000..3dda185 --- /dev/null +++ b/src/sasl.c @@ -0,0 +1,593 @@ +/* sasl.c +** XMPP client library -- SASL authentication helpers +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include + +#include "xmpp.h" +#include "common.h" +#include "sasl.h" +#include "md5.h" + +/* make sure the stdint.h types are available */ +#if defined(_MSC_VER) /* Microsoft Visual C++ */ + typedef signed char int8_t; + typedef short int int16_t; + typedef int int32_t; + typedef __int64 int64_t; + + typedef unsigned char uint8_t; + typedef unsigned short int uint16_t; + typedef unsigned int uint32_t; + /* no uint64_t */ +#else +#include +#endif + + +/** generate authentication string for the SASL PLAIN mechanism */ +char *sasl_plain(xmpp_ctx_t *ctx, const char *authid, const char *password) { + int idlen, passlen; + char *result = NULL; + char *msg; + + /* our message is Base64(authzid,\0,authid,\0,password) + if there is no authzid, that field is left empty */ + + idlen = strlen(authid); + passlen = strlen(password); + msg = xmpp_alloc(ctx, 2 + idlen + passlen); + if (msg != NULL) { + msg[0] = '\0'; + memcpy(msg+1, authid, idlen); + msg[1+idlen] = '\0'; + memcpy(msg+1+idlen+1, password, passlen); + result = base64_encode(ctx, msg, 2 + idlen + passlen); + xmpp_free(ctx, msg); + } + + return result; +} + +/** helpers for digest auth */ + +/* create a new, null-terminated string from a substring */ +static char *_make_string(xmpp_ctx_t *ctx, const char *s, const int len) +{ + char *result; + + result = xmpp_alloc(ctx, len + 1); + if (result != NULL) { + memcpy(result, s, len); + result[len] = '\0'; + } + return result; +} + +/* create a new, null-terminated string quoting another string */ +static char *_make_quoted(xmpp_ctx_t *ctx, const char *s) +{ + char *result; + int len = strlen(s); + + result = xmpp_alloc(ctx, len + 3); + if (result != NULL) { + result[0] = '"'; + memcpy(result+1, s, len); + result[len+1] = '"'; + result[len+2] = '\0'; + } + return result; +} + +/* split key, value pairs into a hash */ +static hash_t *_parse_digest_challenge(xmpp_ctx_t *ctx, const char *msg) +{ + hash_t *result; + char *text; + char *key, *value; + char *s, *t; + + text = base64_decode(ctx, msg, strlen(msg)); + if (text == NULL) { + xmpp_error(ctx, "SASL", "couldn't Base64 decode challenge!"); + return NULL; + } + + result = hash_new(ctx, 10, xmpp_free); + if (result != NULL) { + s = text; + while (*s != '\0') { + /* accumulate a key ending at '=' */ + t = s; + while ((*t != '=') && (*t != '\0')) t++; + if (*t == '\0') break; /* bad string */ + key = _make_string(ctx, s, (t-s)); + if (key == NULL) break; + /* advance our start pointer past the key */ + s = t + 1; + /* accumulate a value ending in ',' or '\0' */ + t = s; + while ((*t != ',') && (*t != '\0')) t++; + /* trim quotes if they occur */ + if (((*s == '\'') && (*(t-1) == '\'')) || + ((*s == '"') && (*(t-1) == '"'))) { + value = _make_string(ctx, s+1, (t-s-2)); + } else { + value = _make_string(ctx, s, (t-s)); + } + if (value == NULL) { + xmpp_free(ctx, key); + break; + } + /* TODO: check for collisions per spec */ + hash_add(result, key, value); + /* hash table now owns the value, free the key */ + xmpp_free(ctx, key); + /* advance past the value and any trailing comma */ + s = (*t) ? t + 1 : t; + } + } + xmpp_free(ctx, text); + + return result; +} + +/** expand a 16 byte MD5 digest to a 32 byte hex representation */ +static void _digest_to_hex(const char *digest, char *hex) +{ + int i; + const char hexdigit[] = "0123456789abcdef"; + + for (i = 0; i < 16; i++) { + *hex++ = hexdigit[ (digest[i] >> 4) & 0x0F ]; + *hex++ = hexdigit[ digest[i] & 0x0F ]; + } +} + +/** append 'key="value"' to a buffer, growing as necessary */ +static char *_add_key(xmpp_ctx_t *ctx, hash_t *table, const char *key, + char *buf, int *len, int quote) +{ + int olen,nlen; + int keylen, valuelen; + const char *value, *qvalue; + char *c; + + /* allocate a zero-length string if necessary */ + if (buf == NULL) { + buf = xmpp_alloc(ctx, 1); + buf[0] = '\0'; + } + if (buf == NULL) return NULL; + + /* get current string length */ + olen = strlen(buf); + value = hash_get(table, key); + if (value == NULL) { + xmpp_error(ctx, "SASL", "couldn't retrieve value for '%s'", key); + value = "\"\""; + } + if (quote) { + qvalue = _make_quoted(ctx, value); + } else { + qvalue = value; + } + /* added length is key + '=' + value */ + /* (+ ',' if we're not the first entry */ + keylen = strlen(key); + valuelen = strlen(qvalue); + nlen = (olen ? 1 : 0) + keylen + 1 + valuelen + 1; + buf = xmpp_realloc(ctx, buf, olen+nlen); + + if (buf != NULL) { + c = buf + olen; + if (olen) *c++ = ','; + memcpy(c, key, keylen); c += keylen; + *c++ = '='; + memcpy(c, qvalue, valuelen); c += valuelen; + *c++ = '\0'; + } + + if (quote) xmpp_free(ctx, (char *)qvalue); + + return buf; +} + +/** generate auth response string for the SASL DIGEST-MD5 mechanism */ +char *sasl_digest_md5(xmpp_ctx_t *ctx, const char *challenge, + const char *jid, const char *password) { + hash_t *table; + char *result = NULL; + char *node, *domain; + char *value; + unsigned char *A1, *A2; /* MD5 hashed versions */ + char *response; + int rlen; + struct MD5Context MD5; + unsigned char digest[16]; + char hex[32]; + + /* our digest response is + Hex( KD( HEX(MD5(A1)), + nonce ':' nc ':' cnonce ':' qop ':' HEX(MD5(A2)) + )) + + where KD(k, s) = MD5(k ':' s), + A1 = MD5( node ':' domain ':' password ) ':' nonce ':' cnonce + A2 = "AUTHENTICATE" ':' "xmpp/" domain + + If there is an authzid it is ':'-appended to A1 */ + + /* parse the challenge */ + table = _parse_digest_challenge(ctx, challenge); + if (table == NULL) { + xmpp_error(ctx, "SASL", "couldn't parse digest challenge"); + return NULL; + } + + node = xmpp_jid_node(ctx, jid); + domain = xmpp_jid_domain(ctx, jid); + + /* add our response fields */ + hash_add(table, "username", xmpp_strdup(ctx, node)); + /* TODO: generate a random cnonce */ + hash_add(table, "cnonce", xmpp_strdup(ctx, "00DEADBEEF00")); + hash_add(table, "nc", xmpp_strdup(ctx, "00000001")); + hash_add(table, "qop", xmpp_strdup(ctx, "auth")); + value = xmpp_alloc(ctx, 5 + strlen(domain) + 1); + memcpy(value, "xmpp/", 5); + memcpy(value+5, domain, strlen(domain)); + value[5+strlen(domain)] = '\0'; + hash_add(table, "digest-uri", value); + + /* generate response */ + + /* construct MD5(A1) */ + MD5Init(&MD5); + MD5Update(&MD5, node, strlen(node)); + MD5Update(&MD5, ":", 1); + MD5Update(&MD5, domain, strlen(domain)); + MD5Update(&MD5, ":", 1); + MD5Update(&MD5, password, strlen(password)); + MD5Final(digest, &MD5); + + MD5Init(&MD5); + MD5Update(&MD5, digest, 16); + MD5Update(&MD5, ":", 1); + value = hash_get(table, "nonce"); + MD5Update(&MD5, value, strlen(value)); + MD5Update(&MD5, ":", 1); + value = hash_get(table, "cnonce"); + MD5Update(&MD5, value, strlen(value)); + MD5Final(digest, &MD5); + + A1 = xmpp_alloc(ctx, 16); + memcpy(A1, digest, 16); + + /* construct MD5(A2) */ + MD5Init(&MD5); + MD5Update(&MD5, "AUTHENTICATE:", 13); + value = hash_get(table, "digest-uri"); + MD5Update(&MD5, value, strlen(value)); + MD5Final(digest, &MD5); + + A2 = xmpp_alloc(ctx, 16); + memcpy(A2, digest, 16); + + /* construct response */ + MD5Init(&MD5); + _digest_to_hex(A1, hex); + MD5Update(&MD5, hex, 32); + MD5Update(&MD5, ":", 1); + value = hash_get(table, "nonce"); + MD5Update(&MD5, value, strlen(value)); + MD5Update(&MD5, ":", 1); + MD5Update(&MD5, "00000001", 8); + MD5Update(&MD5, ":", 1); + value = hash_get(table, "cnonce"); + MD5Update(&MD5, value, strlen(value)); + MD5Update(&MD5, ":", 1); + value = hash_get(table, "qop"); + MD5Update(&MD5, value, strlen(value)); + MD5Update(&MD5, ":", 1); + _digest_to_hex(A2, hex); + MD5Update(&MD5, hex, 32); + MD5Final(digest, &MD5); + + response = xmpp_alloc(ctx, 32+1); + _digest_to_hex(digest, hex); + memcpy(response, hex, 32); + response[32] = '\0'; + hash_add(table, "response", response); + + xmpp_free(ctx, A1); + xmpp_free(ctx, A2); + + /* construct reply */ + result = NULL; + rlen = 0; + result = _add_key(ctx, table, "username", result, &rlen, 1); + result = _add_key(ctx, table, "realm", result, &rlen, 1); + result = _add_key(ctx, table, "nonce", result, &rlen, 1); + result = _add_key(ctx, table, "cnonce", result, &rlen, 1); + result = _add_key(ctx, table, "nc", result, &rlen, 0); + result = _add_key(ctx, table, "qop", result, &rlen, 0); + result = _add_key(ctx, table, "digest-uri", result, &rlen, 1); + result = _add_key(ctx, table, "response", result, &rlen, 0); + result = _add_key(ctx, table, "charset", result, &rlen, 0); + + xmpp_free(ctx, node); + xmpp_free(ctx, domain); + hash_release(table); /* also frees value strings */ + + /* reuse response for the base64 encode of our result */ + response = base64_encode(ctx, result, strlen(result)); + xmpp_free(ctx, result); + + return response; +} + + +/** Base64 encoding routines. Implemented according to RFC 3548 */ + +/** map of all byte values to the base64 values, or to + '65' which indicates an invalid character. '=' is '64' */ +static const char _base64_invcharmap[256] = { + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,62, 65,65,65,63, + 52,53,54,55, 56,57,58,59, 60,61,65,65, 65,64,65,65, + 65, 0, 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,65, 65,65,65,65, + 65,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,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65 +}; + +/** map of all 6-bit values to their corresponding byte + in the base64 alphabet. Padding char is the value '64' */ +static const char _base64_charmap[65] = { + 'A','B','C','D', 'E','F','G','H', + 'I','J','K','L', 'M','N','O','P', + 'Q','R','S','T', 'U','V','W','X', + 'Y','Z','a','b', 'c','d','e','f', + 'g','h','i','j', 'k','l','m','n', + 'o','p','q','r', 's','t','u','v', + 'w','x','y','z', '0','1','2','3', + '4','5','6','7', '8','9','+','/', + '=' +}; + +int base64_encoded_len(xmpp_ctx_t *ctx, const int len) +{ + /* encoded steam is 4 bytes for every three, rounded up */ + return ((len + 2)/3) << 2; +} + +char *base64_encode(xmpp_ctx_t *ctx, + const unsigned char * const buffer, const int len) +{ + int clen; + char *cbuf, *c; + uint32_t word, hextet; + int i; + + clen = base64_encoded_len(ctx, len); + cbuf = xmpp_alloc(ctx, clen + 1); + if (cbuf != NULL) { + c = cbuf; + /* loop over data, turning every 3 bytes into 4 characters */ + for (i = 0; i < len - 2; i += 3) { + word = buffer[i] << 16 | buffer[i+1] << 8 | buffer[i+2]; + hextet = (word & 0x00FC0000) >> 18; + *c++ = _base64_charmap[hextet]; + hextet = (word & 0x0003F000) >> 12; + *c++ = _base64_charmap[hextet]; + hextet = (word & 0x00000FC0) >> 6; + *c++ = _base64_charmap[hextet]; + hextet = (word & 0x000003F); + *c++ = _base64_charmap[hextet]; + } + /* zero, one or two bytes left */ + switch (len - i) { + case 0: + break; + case 1: + hextet = (buffer[len-1] & 0xFC) >> 2; + *c++ = _base64_charmap[hextet]; + hextet = (buffer[len-1] & 0x02) << 6; + *c++ = _base64_charmap[hextet]; + *c++ = _base64_charmap[64]; /* pad */ + *c++ = _base64_charmap[64]; /* pad */ + break; + case 2: + hextet = (buffer[len-2] & 0xFC) >> 2; + *c++ = _base64_charmap[hextet]; + hextet = ((buffer[len-2] & 0x03) << 4) | + ((buffer[len-1] & 0xF0) >> 4); + *c++ = _base64_charmap[hextet]; + hextet = (buffer[len-1] & 0x0F) << 2; + *c++ = _base64_charmap[hextet]; + *c++ = _base64_charmap[64]; /* pad */ + break; + } + /* add a terminal null */ + *c = '\0'; + } + + return cbuf; +} + +int base64_decoded_len(xmpp_ctx_t *ctx, + const char * const buffer, const int len) +{ + int nudge; + int c; + + /* count the padding characters for the remainder */ + nudge = -1; + c = _base64_invcharmap[(int)buffer[len-1]]; + if (c < 64) nudge = 0; + else if (c == 64) { + c = _base64_invcharmap[(int)buffer[len-2]]; + if (c < 64) nudge = 2; + else if (c == 64) { + c = _base64_invcharmap[(int)buffer[len-3]]; + if (c < 64) nudge = 1; + } + } + if (nudge < 0) return 0; /* reject bad coding */ + + /* decoded steam is 3 bytes for every four */ + return 3 * ((len + 3) >> 2) + nudge; +} + +unsigned char *base64_decode(xmpp_ctx_t *ctx, + const char * const buffer, const int len) +{ + int dlen; + unsigned char *dbuf, *d; + uint32_t word, hextet; + int i; + + /* len must be a multiple of 4 */ + if (len & 0x03) return NULL; + + dlen = base64_decoded_len(ctx, buffer, len); + dbuf = xmpp_alloc(ctx, dlen + 1); + if (dbuf != NULL) { + d = dbuf; + /* loop over each set of 4 characters, decoding 3 bytes */ + for (i = 0; i < len - 3; i += 4) { + hextet = _base64_invcharmap[(int)buffer[i]]; + if (hextet & 0xC0) break; + word = hextet << 18; + hextet = _base64_invcharmap[(int)buffer[i+1]]; + if (hextet & 0xC0) break; + word |= hextet << 12; + hextet = _base64_invcharmap[(int)buffer[i+2]]; + if (hextet & 0xC0) break; + word |= hextet << 6; + hextet = _base64_invcharmap[(int)buffer[i+3]]; + if (hextet & 0xC0) break; + word |= hextet; + *d++ = (word & 0x00FF0000) >> 16; + *d++ = (word & 0x0000FF00) >> 8; + *d++ = (word & 0x000000FF); + } + if (hextet > 64) goto _base64_decode_error; + /* handle the remainder */ + switch (dlen % 3) { + case 0: + /* nothing to do */ + break; + case 1: + /* redo the last quartet, checking for correctness */ + hextet = _base64_invcharmap[(int)buffer[len-4]]; + if (hextet & 0xC0) goto _base64_decode_error; + word = hextet << 2; + hextet = _base64_invcharmap[(int)buffer[len-3]]; + if (hextet & 0xC0) goto _base64_decode_error; + word |= hextet >> 4; + *d++ = word & 0xFF; + hextet = _base64_invcharmap[(int)buffer[len-2]]; + if (hextet != 64) goto _base64_decode_error; + hextet = _base64_invcharmap[(int)buffer[len-1]]; + if (hextet != 64) goto _base64_decode_error; + break; + case 2: + /* redo the last quartet, checking for correctness */ + hextet = _base64_invcharmap[(int)buffer[len-4]]; + if (hextet & 0xC0) goto _base64_decode_error; + word = hextet << 10; + hextet = _base64_invcharmap[(int)buffer[len-3]]; + if (hextet & 0xC0) goto _base64_decode_error; + word |= hextet << 4; + hextet = _base64_invcharmap[(int)buffer[len-2]]; + if (hextet & 0xC0) goto _base64_decode_error; + word |= hextet >> 2; + *d++ = (word & 0xFF00) >> 8; + *d++ = (word & 0x00FF); + hextet = _base64_invcharmap[(int)buffer[len-1]]; + if (hextet != 64) goto _base64_decode_error; + break; + } + } + *d = '\0'; + return dbuf; + +_base64_decode_error: + /* invalid character; abort decoding! */ + xmpp_free(ctx, dbuf); + return NULL; +} + +/*** self tests ***/ +#ifdef TEST + +#include + +int test_charmap_identity(void) +{ + int i, v, u; + + for (i = 0; i < 65; i++) { + v = _base64_charmap[i]; + if (v > 255) return 1; + u = _base64_invcharmap[v]; +/* printf("map: %d -> %d -> %d\n", i, v, u); */ + if (u != i) return 1; + } + + return 0; +} + +int test_charmap_range(void) +{ + int i, v; + + for (i = 64; i < 256; i++) { + v = _base64_invcharmap[i]; + if (i < 64) return 1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret = 0; + + printf("testing charmap identity..."); + ret = test_charmap_identity(); + if (ret) return ret; + printf(" ok.\n"); + + printf("testing charmap range..."); + ret = test_charmap_range(); + if (ret) return ret; + printf(" ok.\n"); + + printf("no error\n"); + return 0; +} + +#endif /* TEST */ diff --git a/src/sasl.h b/src/sasl.h new file mode 100644 index 0000000..e96392f --- /dev/null +++ b/src/sasl.h @@ -0,0 +1,40 @@ +/* sasl.h +** XMPP client library -- SASL authentication helpers +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#ifndef __LIBXMPP_SASL_H__ +#define __LIBXMPP_SASL_H__ + +#include "xmpp.h" + +/** low-level sasl routines */ + +char *sasl_plain(xmpp_ctx_t *ctx, const char *authid, const char *password); +char *sasl_digest_md5(xmpp_ctx_t *ctx, const char *challenge, + const char *jid, const char *password); + + +/** Base64 encoding routines. Implemented according to RFC 3548 */ + +int base64_encoded_len(xmpp_ctx_t *ctx, const int len); + +char *base64_encode(xmpp_ctx_t *ctx, + const unsigned char * const buffer, const int len); + +int base64_decoded_len(xmpp_ctx_t *ctx, + const char * const buffer, const int len); + +unsigned char *base64_decode(xmpp_ctx_t *ctx, + const char * const buffer, const int len); + +#endif /* _LIBXMPP_SASL_H__ */ diff --git a/src/sha1.c b/src/sha1.c new file mode 100644 index 0000000..f2dcc03 --- /dev/null +++ b/src/sha1.c @@ -0,0 +1,385 @@ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +----------------- +Modified 7/98 +By James H. Brown +Still 100% Public Domain + +Corrected a problem which generated improper hash values on 16 bit machines +Routine SHA1Update changed from + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int +len) +to + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned +long len) + +The 'len' parameter was declared an int which works fine on 32 bit machines. +However, on 16 bit machines an int is too small for the shifts being done +against +it. This caused the hash function to generate incorrect values if len was +greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update(). + +Since the file IO in main() reads 16K at a time, any file 8K or larger would +be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million +"a"s). + +I also changed the declaration of variables i & j in SHA1Update to +unsigned long from unsigned int for the same reason. + +These changes should make no difference to any 32 bit implementations since +an +int and a long are the same size in those environments. + +-- +I also corrected a few compiler warnings generated by Borland C. +1. Added #include for exit() prototype +2. Removed unused variable 'j' in SHA1Final +3. Changed exit(0) to return(0) at end of main. + +ALL changes I made can be located by searching for comments containing 'JHB' +----------------- +Modified 8/98 +By Steve Reid +Still 100% public domain + +1- Removed #include and used return() instead of exit() +2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall) +3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net + +----------------- +Modified 4/01 +By Saul Kravitz +Still 100% PD +Modified to run on Compaq Alpha hardware. + +----------------- +Modified 07/2002 +By Ralph Giles +Still 100% public domain +modified for use with stdint types, autoconf +code cleanup, removed attribution comments +switched SHA1Final() argument order for consistency +use SHA1_ prefix for public api +move public api to sha1.h +*/ + +/* +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define SHA1HANDSOFF */ + +#include +#include + +/* make sure the stdint.h types are available */ +#if defined(_MSC_VER) /* Microsoft Visual C++ */ + typedef signed char int8_t; + typedef short int int16_t; + typedef int int32_t; + typedef __int64 int64_t; + + typedef unsigned char uint8_t; + typedef unsigned short int uint16_t; + typedef unsigned int uint32_t; + /* no uint64_t */ +#else +#include +#endif + +#include "sha1.h" + +void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +/* FIXME: can we do this in an endian-proof way? */ +#ifdef WORDS_BIGENDIAN +#define blk0(i) block->l[i] +#else +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +#ifdef VERBOSE /* SAK */ +void SHAPrintContext(SHA1_CTX *context, char *msg){ + printf("%s (%d,%d) %x %x %x %x %x\n", + msg, + context->count[0], context->count[1], + context->state[0], + context->state[1], + context->state[2], + context->state[3], + context->state[4]); +} +#endif /* VERBOSE */ + +/* Hash a single 512-bit block. This is the core of the algorithm. */ +void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]) +{ + uint32_t a, b, c, d, e; + typedef union { + uint8_t c[64]; + uint32_t l[16]; + } CHAR64LONG16; + CHAR64LONG16* block; + +#ifdef SHA1HANDSOFF + static uint8_t workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); +#else + block = (CHAR64LONG16*)buffer; +#endif + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1Init - Initialize new context */ +void SHA1_Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ +void SHA1_Update(SHA1_CTX* context, const uint8_t* data, const size_t len) +{ + size_t i, j; + +#ifdef VERBOSE + SHAPrintContext(context, "before"); +#endif + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1_Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1_Transform(context->state, data + i); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); + +#ifdef VERBOSE + SHAPrintContext(context, "after "); +#endif +} + + +/* Add padding and return the message digest. */ +void SHA1_Final(SHA1_CTX* context, uint8_t digest[SHA1_DIGEST_SIZE]) +{ + uint32_t i; + uint8_t finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1_Update(context, (uint8_t *)"\200", 1); + while ((context->count[0] & 504) != 448) { + SHA1_Update(context, (uint8_t *)"\0", 1); + } + SHA1_Update(context, finalcount, 8); /* Should cause a SHA1_Transform() */ + for (i = 0; i < SHA1_DIGEST_SIZE; i++) { + digest[i] = (uint8_t) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + /* Wipe variables */ + i = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(finalcount, 0, 8); /* SWR */ + +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite its own static vars */ + SHA1_Transform(context->state, context->buffer); +#endif +} + +/*************************************************************/ + +#if 0 +int main(int argc, char** argv) +{ +int i, j; +SHA1_CTX context; +unsigned char digest[SHA1_DIGEST_SIZE], buffer[16384]; +FILE* file; + + if (argc > 2) { + puts("Public domain SHA-1 implementation - by Steve Reid "); + puts("Modified for 16 bit environments 7/98 - by James H. Brown "); /* JHB */ + puts("Produces the SHA-1 hash of a file, or stdin if no file is specified."); + return(0); + } + if (argc < 2) { + file = stdin; + } + else { + if (!(file = fopen(argv[1], "rb"))) { + fputs("Unable to open file.", stderr); + return(-1); + } + } + SHA1_Init(&context); + while (!feof(file)) { /* note: what if ferror(file) */ + i = fread(buffer, 1, 16384, file); + SHA1_Update(&context, buffer, i); + } + SHA1_Final(&context, digest); + fclose(file); + for (i = 0; i < SHA1_DIGEST_SIZE/4; i++) { + for (j = 0; j < 4; j++) { + printf("%02X", digest[i*4+j]); + } + putchar(' '); + } + putchar('\n'); + return(0); /* JHB */ +} +#endif + +/* self test */ + +#ifdef TEST + +static char *test_data[] = { + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "A million repetitions of 'a'"}; +static char *test_results[] = { + "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D", + "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1", + "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F"}; + + +void digest_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE], char *output) +{ + int i,j; + char *c = output; + + for (i = 0; i < SHA1_DIGEST_SIZE/4; i++) { + for (j = 0; j < 4; j++) { + sprintf(c,"%02X", digest[i*4+j]); + c += 2; + } + sprintf(c, " "); + c += 1; + } + *(c - 1) = '\0'; +} + +int main(int argc, char** argv) +{ + int k; + SHA1_CTX context; + uint8_t digest[20]; + char output[80]; + + fprintf(stdout, "verifying SHA-1 implementation... "); + + for (k = 0; k < 2; k++){ + SHA1_Init(&context); + SHA1_Update(&context, (uint8_t*)test_data[k], strlen(test_data[k])); + SHA1_Final(&context, digest); + digest_to_hex(digest, output); + + if (strcmp(output, test_results[k])) { + fprintf(stdout, "FAIL\n"); + fprintf(stderr,"* hash of \"%s\" incorrect:\n", test_data[k]); + fprintf(stderr,"\t%s returned\n", output); + fprintf(stderr,"\t%s is correct\n", test_results[k]); + return (1); + } + } + /* million 'a' vector we feed separately */ + SHA1_Init(&context); + for (k = 0; k < 1000000; k++) + SHA1_Update(&context, (uint8_t*)"a", 1); + SHA1_Final(&context, digest); + digest_to_hex(digest, output); + if (strcmp(output, test_results[2])) { + fprintf(stdout, "FAIL\n"); + fprintf(stderr,"* hash of \"%s\" incorrect:\n", test_data[2]); + fprintf(stderr,"\t%s returned\n", output); + fprintf(stderr,"\t%s is correct\n", test_results[2]); + return (1); + } + + /* success */ + fprintf(stdout, "ok\n"); + return(0); +} +#endif /* TEST */ diff --git a/src/sha1.h b/src/sha1.h new file mode 100644 index 0000000..e57a8c3 --- /dev/null +++ b/src/sha1.h @@ -0,0 +1,27 @@ +/* public api for steve reid's public domain SHA-1 implementation */ +/* this file is in the public domain */ + +#ifndef __SHA1_H +#define __SHA1_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + uint8_t buffer[64]; +} SHA1_CTX; + +#define SHA1_DIGEST_SIZE 20 + +void SHA1_Init(SHA1_CTX* context); +void SHA1_Update(SHA1_CTX* context, const uint8_t* data, const size_t len); +void SHA1_Final(SHA1_CTX* context, uint8_t digest[SHA1_DIGEST_SIZE]); + +#ifdef __cplusplus +} +#endif + +#endif /* __SHA1_H */ diff --git a/src/sock.c b/src/sock.c new file mode 100644 index 0000000..2c1ad8a --- /dev/null +++ b/src/sock.c @@ -0,0 +1,164 @@ +/* sock.c +** XMPP client library -- socket abstraction implementation +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include +#include + +#ifdef _WIN32 +#include +#define snprintf _snprintf +#else +#include +#include +#include +#include +#include +#include +#endif + +#include "sock.h" + +void sock_initialize(void) +{ +#ifdef _WIN32 + WSADATA wsad; + WSAStartup(0x0101, &wsad); +#endif +} + +void sock_shutdown(void) +{ +#ifdef _WIN32 + WSACleanup(); +#endif +} + +int sock_error(void) +{ +#ifdef _WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +static int _in_progress(int error) +{ +#ifdef _WIN32 + return (error == WSAEWOULDBLOCK || error == WSAEINPROGRESS); +#else + return (errno == EINPROGRESS); +#endif +} + +sock_t sock_connect(const char * const host, const unsigned int port) +{ + sock_t sock; + char service[6]; + struct addrinfo *res, *ainfo, hints; + int err; + + sock = -1; + + snprintf(service, 6, "%u", port); + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + + if ((err = getaddrinfo(host, service, &hints, &res)) != 0) + return -1; + + ainfo = res; + while (ainfo) { + if ((sock = socket(ainfo->ai_family, ainfo->ai_socktype, + ainfo->ai_protocol)) >= 0) { + sock_set_nonblocking(sock); + + err = connect(sock, ainfo->ai_addr, ainfo->ai_addrlen); + + if ((err == 0) || (err < 0 && _in_progress(sock_error()))) + break; + } + + ainfo = ainfo->ai_next; + } + + if (res) freeaddrinfo(res); + + return sock; +} + +int sock_close(const sock_t sock) +{ +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +int sock_set_blocking(const sock_t sock) +{ +#ifdef _WIN32 + u_long block = 0; + return ioctlsocket(sock, FIONBIO, &block); +#else + return fcntl(sock, F_SETFL, 0); +#endif +} + +int sock_set_nonblocking(const sock_t sock) +{ +#ifdef _WIN32 + u_long nonblock = 1; + return ioctlsocket(sock, FIONBIO, &nonblock); +#else + return fcntl(sock, F_SETFL, O_NONBLOCK); +#endif +} + +int sock_read(const sock_t sock, void * const buff, const size_t len) +{ + return recv(sock, buff, len, 0); +} + +int sock_write(const sock_t sock, const void * const buff, const size_t len) +{ + return send(sock, buff, len, 0); +} + +int sock_is_recoverable(const int error) +{ +#ifdef _WIN32 + return (error == WSAEINTR || error == WSAEWOULDBLOCK || + error == WSAEINPROGRESS); +#else + return (error == EAGAIN || error == EINTR); +#endif +} + +int sock_connect_error(const sock_t sock) +{ + socklen_t len; + int error, ret; + + len = sizeof(int); + + ret = getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len); + if (ret < 0) return ret; + return error; +} diff --git a/src/sock.h b/src/sock.h new file mode 100644 index 0000000..1cbc1b9 --- /dev/null +++ b/src/sock.h @@ -0,0 +1,42 @@ +/* sock.h +** XMPP client library -- socket abstraction header +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#ifndef __LIBXMPP_SOCK_H__ +#define __LIBXMPP_SOCK_H__ + +#include + +#ifndef _WIN32 +typedef int sock_t; +#else +typedef SOCKET sock_t; +#endif + +void sock_initialize(void); +void sock_shutdown(void); + +int sock_error(void); + +sock_t sock_connect(const char * const host, const unsigned int port); +int sock_close(const sock_t sock); + +int sock_set_blocking(const sock_t sock); +int sock_set_nonblocking(const sock_t sock); +int sock_read(const sock_t sock, void * const buff, const size_t len); +int sock_write(const sock_t sock, const void * const buff, const size_t len); +int sock_is_recoverable(const int error); +/* checks for an error after connect, return 0 if connect successful */ +int sock_connect_error(const sock_t sock); + +#endif /* __LIBXMPP_SOCK_H__ */ diff --git a/src/stanza.c b/src/stanza.c new file mode 100644 index 0000000..2680b82 --- /dev/null +++ b/src/stanza.c @@ -0,0 +1,505 @@ +/* stanza.c +** XMPP client library -- XMPP stanza object and utilities +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include + +#include "xmpp.h" +#include "common.h" +#include "hash.h" + +/* allocate an initialize a blank stanza */ +xmpp_stanza_t *xmpp_stanza_new(xmpp_ctx_t *ctx) +{ + xmpp_stanza_t *stanza; + + stanza = xmpp_alloc(ctx, sizeof(xmpp_stanza_t)); + if (stanza != NULL) { + stanza->ref = 1; + stanza->ctx = ctx; + stanza->type = XMPP_STANZA_UNKNOWN; + stanza->prev = NULL; + stanza->next = NULL; + stanza->children = NULL; + stanza->parent = NULL; + stanza->data = NULL; + stanza->attributes = NULL; + } + + return stanza; +} + +/* clone a stanza */ +xmpp_stanza_t *xmpp_stanza_clone(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *child; + + stanza->ref++; + + /* clone all children */ + for (child = stanza->children; child; child = child->next) + xmpp_stanza_clone(child); + + return stanza; +} + +/* copies and stanza and all children + * this function returns a new stanza copied from stanza. the new + * stanza will have no parent and no siblings. the caller is given + * a reference to this new stanza. this function is used to extract + * a child from one stanza for inclusion in another. */ +xmpp_stanza_t *xmpp_stanza_copy(const xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *copy, *child, *copychild, *tail; + hash_iterator_t *iter; + const char *key; + void *val; + + copy = xmpp_stanza_new(stanza->ctx); + if (!copy) goto copy_error; + + copy->type = stanza->type; + + if (stanza->data) { + copy->data = xmpp_strdup(stanza->ctx, stanza->data); + if (!copy->data) goto copy_error; + } + + if (stanza->attributes) { + copy->attributes = hash_new(stanza->ctx, 8, xmpp_free); + if (!copy->attributes) goto copy_error; + iter = hash_iter_new(stanza->attributes); + if (!iter) { printf("DEBUG HERE\n"); goto copy_error; } + while ((key = hash_iter_next(iter))) { + val = xmpp_strdup(stanza->ctx, + (char *)hash_get(stanza->attributes, key)); + if (!val) goto copy_error; + + if (hash_add(copy->attributes, key, val)) + goto copy_error; + } + hash_iter_release(iter); + } + + tail = copy->children; + for (child = stanza->children; child; child = child->next) { + copychild = xmpp_stanza_copy(child); + if (!copychild) goto copy_error; + copychild->parent = copy; + + if (tail) { + copychild->prev = tail; + tail->next = copychild; + } else + copy->children = copychild; + tail = copychild; + } + + return copy; + +copy_error: + /* release all the hitherto allocated memory */ + if (copy) xmpp_stanza_release(copy); + return NULL; +} + +/* free a stanza object and it's contents */ +void xmpp_stanza_release(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *child, *tchild; + + /* release all children */ + child = stanza->children; + while (child) { + tchild = child; + child = child->next; + xmpp_stanza_release(tchild); + } + + /* release stanza */ + if (stanza->ref > 1) + stanza->ref--; + else { + if (stanza->attributes) hash_release(stanza->attributes); + if (stanza->data) xmpp_free(stanza->ctx, stanza->data); + xmpp_free(stanza->ctx, stanza); + } +} + +/* small helper function */ +static inline void _render_update(int *written, const int length, + const int lastwrite, + size_t *left, char **ptr) +{ + *written += lastwrite; + + if (*written > length) { + *left = 0; + *ptr = NULL; + } else { + *left -= lastwrite; + *ptr = &(*ptr)[lastwrite]; + } +} + +/* always returns number of bytes written or that would have been + * written if the buffer was large enough + * return values < 0 indicate some error occured, + * and return values > buflen indicate buffer was not large enough + */ +static int _render_stanza_recursive(xmpp_stanza_t *stanza, + char * const buf, size_t const buflen) +{ + char *ptr = buf; + size_t left = buflen; + int ret, written; + xmpp_stanza_t *child; + hash_iterator_t *iter; + const char *key; + + written = 0; + + if (stanza->type == XMPP_STANZA_UNKNOWN) return -1; + + if (stanza->type == XMPP_STANZA_TEXT) { + if (!stanza->data) return -2; + + ret = snprintf(ptr, left, "%s", stanza->data); + if (ret < 0) return -4; + _render_update(&written, buflen, ret, &left, &ptr); + } else { /* stanza->type == XMPP_STANZA_TAG */ + if (!stanza->data) return -2; + + /* write begining of tag and attributes */ + ret = snprintf(ptr, left, "<%s", stanza->data); + if (ret < 0) return -4; + _render_update(&written, buflen, ret, &left, &ptr); + + if (stanza->attributes && hash_num_keys(stanza->attributes) > 0) { + iter = hash_iter_new(stanza->attributes); + while ((key = hash_iter_next(iter))) { + ret = snprintf(ptr, left, " %s=\"%s\"", key, + (char *)hash_get(stanza->attributes, key)); + if (ret < 0) return -4; + _render_update(&written, buflen, ret, &left, &ptr); + } + hash_iter_release(iter); + } + + if (!stanza->children) { + /* write end if singleton tag */ + ret = snprintf(ptr, left, "/>"); + if (ret < 0) return -4; + _render_update(&written, buflen, ret, &left, &ptr); + } else { + /* this stanza has child stanzas */ + + /* write end of start tag */ + ret = snprintf(ptr, left, ">"); + if (ret < 0) return -4; + _render_update(&written, buflen, ret, &left, &ptr); + + /* iterate and recurse over child stanzas */ + child = stanza->children; + while (child) { + ret = _render_stanza_recursive(child, ptr, left); + if (ret < 0) return ret; + + _render_update(&written, buflen, ret, &left, &ptr); + + child = child->next; + } + + /* write end tag */ + ret = snprintf(ptr, left, "", stanza->data); + if (ret < 0) return -4; + + _render_update(&written, buflen, ret, &left, &ptr); + } + } + + return written; +} + +/* render a stanza to text + * a buffer is allocated big enough to hold the stanza + * and *buf = buffer. the size of buffer filled with data + * is returned in *buflen (does not include trailing \0). + * the returned buffer contains a trailing \0 so the result is + * a valid string. + */ +int xmpp_stanza_to_text(xmpp_stanza_t *stanza, + char ** const buf, + size_t * const buflen) +{ + char *buffer, *tmp; + size_t length; + int ret; + + /* allocate a default sized buffer and attempt to render */ + length = 1024; + buffer = xmpp_alloc(stanza->ctx, length); + if (!buffer) { + *buf = NULL; + *buflen = 0; + return -5; + } + + ret = _render_stanza_recursive(stanza, buffer, length); + if (ret < 0) return ret; + + if (ret > length - 1) { + tmp = xmpp_realloc(stanza->ctx, buffer, ret + 1); + if (!tmp) { + xmpp_free(stanza->ctx, buffer); + *buf = NULL; + *buflen = 0; + return -5; + } + length = ret; + buffer = tmp; + buffer[length-1] = 0; + + ret = _render_stanza_recursive(stanza, buffer, length); + if (ret > length) return -6; + } + + length = ret + 1; + buffer[length - 1] = 0; + + *buf = buffer; + *buflen = length - 1; + + return 0; +} + +void xmpp_stanza_set_name(xmpp_stanza_t *stanza, + const char * const name) +{ + if (stanza->type == XMPP_STANZA_TEXT) return; + + if (stanza->data) xmpp_free(stanza->ctx, stanza->data); + + stanza->type = XMPP_STANZA_TAG; + stanza->data = xmpp_strdup(stanza->ctx, name); +} + +char *xmpp_stanza_get_name(xmpp_stanza_t * const stanza) +{ + if (stanza->type == XMPP_STANZA_TEXT) return NULL; + return stanza->data; +} + +/* convinience function to copy attributes from the xml parser + * callback into a stanza. this replaces all previous attributes */ +void xmpp_stanza_set_attributes(xmpp_stanza_t *stanza, + const char * const * const attr) +{ + int i; + char *value; + + if (stanza->attributes != NULL) + hash_release(stanza->attributes); + + stanza->attributes = hash_new(stanza->ctx, 8, xmpp_free); + if (!stanza->attributes) return; + + for (i = 0; attr[i]; i += 2) { + value = xmpp_strdup(stanza->ctx, attr[i + 1]); + if (!value) { + /* FIXME: memory allocation error */ + continue; + } + hash_add(stanza->attributes, attr[i], value); + } +} + +void xmpp_stanza_set_attribute(xmpp_stanza_t * const stanza, + const char * const key, + const char * const value) +{ + char *val; + + if (stanza->type != XMPP_STANZA_TAG) return; + + if (!stanza->attributes) { + stanza->attributes = hash_new(stanza->ctx, 8, xmpp_free); + if (!stanza->attributes) return; + } + + val = xmpp_strdup(stanza->ctx, value); + if (!val) return; + + hash_add(stanza->attributes, key, val); +} + +void xmpp_stanza_set_ns(xmpp_stanza_t * const stanza, + const char * const ns) +{ + xmpp_stanza_set_attribute(stanza, "xmlns", ns); +} + +void xmpp_stanza_add_child(xmpp_stanza_t *stanza, xmpp_stanza_t *child) +{ + xmpp_stanza_t *s; + + /* get a reference to the child */ + xmpp_stanza_clone(child); + + child->parent = stanza; + + if (!stanza->children) + stanza->children = child; + else { + s = stanza->children; + while (s->next) s = s->next; + s->next = child; + child->prev = s; + } +} + +void xmpp_stanza_set_text(xmpp_stanza_t *stanza, + const char * const text) +{ + if (stanza->type == XMPP_STANZA_TAG) return; + + stanza->type = XMPP_STANZA_TEXT; + + if (stanza->data) xmpp_free(stanza->ctx, stanza->data); + stanza->data = xmpp_strdup(stanza->ctx, text); +} + +void xmpp_stanza_set_text_with_size(xmpp_stanza_t *stanza, + const char * const text, + const size_t size) +{ + if (stanza->type == XMPP_STANZA_TAG) return; + + stanza->type = XMPP_STANZA_TEXT; + + if (stanza->data) xmpp_free(stanza->ctx, stanza->data); + stanza->data = xmpp_alloc(stanza->ctx, size + 1); + if (!stanza->data) return; + + memcpy(stanza->data, text, size); + stanza->data[size] = 0; +} + +char *xmpp_stanza_get_id(xmpp_stanza_t * const stanza) +{ + if (stanza->type != XMPP_STANZA_TAG) + return NULL; + + if (!stanza->attributes) + return NULL; + + return (char *)hash_get(stanza->attributes, "id"); +} + +char *xmpp_stanza_get_ns(xmpp_stanza_t * const stanza) +{ + if (stanza->type != XMPP_STANZA_TAG) + return NULL; + + if (!stanza->attributes) + return NULL; + + return (char *)hash_get(stanza->attributes, "xmlns"); +} + +char *xmpp_stanza_get_type(xmpp_stanza_t * const stanza) +{ + if (stanza->type != XMPP_STANZA_TAG) + return NULL; + + if (!stanza->attributes) + return NULL; + + return (char *)hash_get(stanza->attributes, "type"); +} + +xmpp_stanza_t *xmpp_stanza_get_child_by_name(xmpp_stanza_t * const stanza, + const char * const name) +{ + xmpp_stanza_t *child; + + for (child = stanza->children; child; child = child->next) { + if (child->type == XMPP_STANZA_TAG && + (strcmp(name, xmpp_stanza_get_name(child)) == 0)) + break; + } + + return child; +} + +xmpp_stanza_t *xmpp_stanza_get_children(xmpp_stanza_t * const stanza) +{ + return stanza->children; +} + +xmpp_stanza_t *xmpp_stanza_get_next(xmpp_stanza_t * const stanza) +{ + return stanza->next; +} + +char *xmpp_stanza_get_text(xmpp_stanza_t * const stanza) +{ + size_t len, clen; + xmpp_stanza_t *child; + char *text; + + len = 0; + for (child = stanza->children; child; child = child->next) + if (child->type == XMPP_STANZA_TEXT) + len += strlen(child->data); + + text = (char *)xmpp_alloc(stanza->ctx, len + 1); + if (!text) return NULL; + + len = 0; + for (child = stanza->children; child; child = child->next) + if (child->type == XMPP_STANZA_TEXT) { + clen = strlen(child->data); + memcpy(&text[len], child->data, clen); + len += clen; + } + + text[len] = 0; + + return text; +} + +void xmpp_stanza_set_id(xmpp_stanza_t * const stanza, + const char * const id) +{ + xmpp_stanza_set_attribute(stanza, "id", id); +} + +void xmpp_stanza_set_type(xmpp_stanza_t * const stanza, + const char * const type) +{ + xmpp_stanza_set_attribute(stanza, "type", type); +} + +char *xmpp_stanza_get_attribute(xmpp_stanza_t * const stanza, + const char * const name) +{ + if (stanza->type != XMPP_STANZA_TAG) + return NULL; + + if (!stanza->attributes) + return NULL; + + return hash_get(stanza->attributes, name); +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..ce14e7c --- /dev/null +++ b/src/util.c @@ -0,0 +1,70 @@ +/* util.c +** XMPP client library -- various utility functions +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +#include "xmpp.h" +#include "common.h" +#include "util.h" + +/** implement our own strdup that uses the ctx allocator */ +char *xmpp_strdup(const xmpp_ctx_t * const ctx, const char * const s) +{ + size_t len; + char *copy; + + len = strlen(s); + copy = xmpp_alloc(ctx, len + 1); + if (!copy) { + xmpp_error(ctx, "xmpp", "failed to allocate required memory"); + return NULL; + } + + memcpy(copy, s, len + 1); + + return copy; +} + +uint64_t time_stamp(void) +{ +#ifdef _WIN32 + return timeGetTime(); +#else + struct timeval tv; + + gettimeofday(&tv, NULL); + + return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000; +#endif +} + +uint64_t time_elapsed(uint64_t t1, uint64_t t2) +{ + return (uint64_t)(t2 - t1); +} + +void disconnect_mem_error(xmpp_conn_t * const conn) +{ + xmpp_error(conn->ctx, "xmpp", "Memory allocation error"); + xmpp_disconnect(conn); +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..fd555d5 --- /dev/null +++ b/src/util.h @@ -0,0 +1,26 @@ +/* util.h +** XMPP client library -- various utility functions +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#ifndef __LIBXMPP_UTIL_H__ +#define __LIBXMPP_UTIL_H__ + +#ifndef _WIN32 +#include +#endif + +/* timing functions */ +uint64_t time_stamp(void); +uint64_t time_elapsed(uint64_t t1, uint64_t t2); + +#endif /* __LIBXMPP_UTIL_H__ */ diff --git a/tests/test_base64.c b/tests/test_base64.c new file mode 100644 index 0000000..cda7498 --- /dev/null +++ b/tests/test_base64.c @@ -0,0 +1,151 @@ +/* test_base64.c +** XMPP client library -- test routines for the base64 codec +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include + +#include "xmpp.h" +#include "common.h" +#include "sasl.h" + +static const unsigned char text_1[] = "How now brown cow?"; +static const char base64_1[] = "SG93IG5vdyBicm93biBjb3c/"; +static const unsigned char text_2[] = {0x14,0xfb,0x9c,0x03,0xd9,0x7e,0x00}; +static const char base64_2[] = "FPucA9l+"; +static const unsigned char text_3[] = + "From rest and sleep, which but thy pictures be, " + "Much pleasure; then from thee much more must flow, " + "And soonest our best men with thee do go, " + "Rest of their bones, and soul's delivery."; +static const char base64_3[] = + "RnJvbSByZXN0IGFuZCBzbGVlcCwgd2hpY2ggYnV0IHRoeSBwaWN0dXJl" + "cyBiZSwgTXVjaCBwbGVhc3VyZTsgdGhlbiBmcm9tIHRoZWUgbXVjaCBt" + "b3JlIG11c3QgZmxvdywgQW5kIHNvb25lc3Qgb3VyIGJlc3QgbWVuIHdp" + "dGggdGhlZSBkbyBnbywgUmVzdCBvZiB0aGVpciBib25lcywgYW5kIHNv" + "dWwncyBkZWxpdmVyeS4="; +static const unsigned char text_4[] = + {0xd6, 0x2f, 0x27, 0x49, 0x7e, 0xdd, 0xf3, 0xd5, + 0x41, 0xbc, 0x1b, 0xe9, 0xdf, 0xe9, 0xb3, 0x08, 0x00}; +static const char base64_4[] = "1i8nSX7d89VBvBvp3+mzCA=="; +static const char text_5[] = + "realm=\"chesspark.com\",nonce=\"b243c0d663257a9149999cef2f83a22116559e93\",qop=\"auth\",charset=utf-8,algorithm=md5-sess"; +static const char base64_5[] = + "cmVhbG09ImNoZXNzcGFyay5jb20iLG5vbmNlPSJiMjQzYzBkNjYzMjU3" + "YTkxNDk5OTljZWYyZjgzYTIyMTE2NTU5ZTkzIixxb3A9ImF1dGgiLGNo" + "YXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNz"; + +int test_encode(xmpp_ctx_t *ctx) +{ + char *result; + result = base64_encode(ctx, text_1, strlen(text_1)); + if (result == NULL) return 2; + if (strlen(result) != strlen(base64_1)) return 1; + if (strncmp(base64_1, result, strlen(base64_1))) return 1; + xmpp_free(ctx,result); + + result = base64_encode(ctx, text_2, strlen(text_2)); + if (result == NULL) return 2; + if (strlen(result) != strlen(base64_2)) return 1; + if (strncmp(base64_2, result, strlen(base64_2))) return 1; + xmpp_free(ctx,result); + + result = base64_encode(ctx, text_3, strlen(text_3)); + if (result == NULL) return 2; + if (strlen(result) != strlen(base64_3)) return 1; + if (strncmp(base64_3, result, strlen(base64_3))) return 1; + xmpp_free(ctx,result); + + result = base64_encode(ctx, text_4, strlen(text_4)); + if (result == NULL) return 2; + if (strlen(result) != strlen(base64_4)) return 1; + if (strncmp(base64_4, result, strlen(base64_4))) return 1; + xmpp_free(ctx,result); + + result = base64_encode(ctx, text_5, strlen(text_5)); + if (result == NULL) return 2; + if (strlen(result) != strlen(base64_5)) return 1; + if (strncmp(base64_5, result, strlen(base64_5))) return 1; + xmpp_free(ctx,result); + + return 0; +} + + +int test_decode(xmpp_ctx_t *ctx) +{ + unsigned char *result; + + result = base64_decode(ctx, base64_1, strlen(base64_1)); + if (result == NULL) return 2; + if (strlen(result) != strlen(text_1)) return 1; + if (strncmp(text_1, result, strlen(text_1))) return 1; + xmpp_free(ctx,result); + + result = base64_decode(ctx, base64_2, strlen(base64_2)); + if (result == NULL) return 2; + if (strlen(result) != strlen(text_2)) return 1; + if (strncmp(text_2, result, strlen(text_2))) return 1; + xmpp_free(ctx,result); + + result = base64_decode(ctx, base64_3, strlen(base64_3)); + if (result == NULL) return 2; + if (strlen(result) != strlen(text_3)) return 1; + if (strncmp(text_3, result, strlen(text_3))) return 1; + xmpp_free(ctx,result); + + result = base64_decode(ctx, base64_4, strlen(base64_4)); + if (result == NULL) return 2; + if (strlen(result) != strlen(text_4)) return 1; + if (strncmp(text_4, result, strlen(text_4))) return 1; + xmpp_free(ctx,result); + + result = base64_decode(ctx, base64_5, strlen(base64_5)); + if (result == NULL) return 2; + if (strlen(result) != strlen(text_5)) return 1; + if (strncmp(text_5, result, strlen(text_5))) return 1; + xmpp_free(ctx,result); + + return 0; +} + + +int main(int argc, char *argv[]) +{ + xmpp_ctx_t *ctx; + int ret; + + printf("allocating context... "); + ctx = xmpp_ctx_new(NULL, NULL); + if (ctx == NULL) printf("failed to create context\n"); + if (ctx == NULL) return -1; + printf("ok.\n"); + + printf("testing encode... "); + ret = test_encode(ctx); + if (ret) printf("base64_encode failed!\n"); + if (ret) return ret; + printf("ok.\n"); + + printf("testing decode... "); + ret = test_decode(ctx); + if (ret) printf("base64_decode failed!\n"); + if (ret) return ret; + printf("ok.\n"); + + printf("freeing context... "); + xmpp_ctx_free(ctx); + printf("ok.\n"); + + return ret; +} diff --git a/tests/test_ctx.c b/tests/test_ctx.c new file mode 100644 index 0000000..d233c80 --- /dev/null +++ b/tests/test_ctx.c @@ -0,0 +1,102 @@ +/* test_ctx.c +** XMPP client library -- test routines for the library run-time context +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include +#include + +#include "xmpp.h" +#include "common.h" + +static int log_called = 0; +static int mem_alloc_called = 0; +static int mem_free_called = 0; +static int mem_realloc_called = 0; + +void *my_alloc(const size_t size) +{ + mem_alloc_called++; + return malloc(size); +} + +void my_free(void *p) +{ + mem_free_called++; + return free(p); +} + +void *my_realloc(void *p, const size_t size) +{ + mem_realloc_called++; + return realloc(p, size); +} + +void my_logger(void * const userdata, const xmpp_log_level_t level, + const char * const area, const char * const msg) +{ + if (strcmp((char *)userdata, "asdf") == 0 && level == XMPP_LEVEL_DEBUG + && strcmp(area, "test") == 0 && strcmp(msg, "hello") == 0) + log_called++; +} + +int main(int argc, char **argv) +{ + xmpp_ctx_t *ctx; + xmpp_mem_t mymem; + xmpp_log_t mylog; + char my_str[5] = "asdf"; + void *testptr1, *testptr2; + + ctx = xmpp_ctx_new(NULL, NULL); + if (ctx == NULL) return 1; + + /* destroy context */ + xmpp_ctx_free(ctx); + + /* setup our memory handler */ + mymem.alloc = my_alloc; + mymem.free = my_free; + mymem.realloc = my_realloc; + + /* setup our logger */ + mylog.handler = my_logger; + mylog.userdata = my_str; + + ctx = xmpp_ctx_new(&mymem, &mylog); + xmpp_debug(ctx, "test", "hello"); + + testptr1 = xmpp_alloc(ctx, 1024); + if (testptr1 == NULL) { + xmpp_ctx_free(ctx); + return 1; + } + + testptr2 = xmpp_realloc(ctx, testptr1, 2048); + if (testptr2 == NULL) { + xmpp_free(ctx, testptr1); + xmpp_ctx_free(ctx); + return 1; + } + + xmpp_free(ctx, testptr2); + + xmpp_ctx_free(ctx); + + /* check for test failure */ + if (!(log_called && mem_alloc_called && mem_realloc_called && + mem_free_called)) + return 1; + + return 0; +} diff --git a/tests/test_hash.c b/tests/test_hash.c new file mode 100644 index 0000000..1620209 --- /dev/null +++ b/tests/test_hash.c @@ -0,0 +1,141 @@ +/* test_hash.c +** XMPP client library -- self-test for the hash-table implementation +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include +#include + +#include "xmpp.h" +#include "common.h" +#include "hash.h" + +#define TABLESIZE 100 +#define TESTSIZE 500 + +/* static test data */ +const int nkeys = 5; +const char *keys[] = { + "foo", "bar", "baz", "quux", "xyzzy" +}; +const char *values[] = { + "wuzzle", "mug", "canonical", "rosebud", "lottery" +}; + +int main(int argc, char **argv) +{ + xmpp_ctx_t *ctx; + hash_t *table, *clone; + hash_iterator_t *iter; + unsigned int seed; + const char *key; + char *result; + int err = 0; + int i; + + /* initialize random numbers */ + if (argc > 2) { + /* use a seed from the command line */ + seed = (unsigned int)atoi(argv[1]); + } else { + seed = (unsigned int)clock(); + } + /* using random seed 'seed' */ + srand(seed); + + /* allocate a default context */ + ctx = xmpp_ctx_new(NULL, NULL); + if (ctx == NULL) { + /* ctx allocation failed! */ + return -1; + } + + /* allocate a hash table */ + table = hash_new(ctx, TABLESIZE, NULL); + if (table == NULL) { + /* table allocation failed! */ + return 1; + } + + /* test insertion */ + for (i = 0; i < nkeys; i++) { + err = hash_add(table, keys[i], (void*)values[i]); + if (err) return err; + } + + /* test key count */ + if (hash_num_keys(table) != nkeys) { + /* wrong number of keys in table! */ + return 1; + } + + /* test cloning */ + clone = hash_clone(table); + + /* test lookup */ + for (i = 0; i < nkeys; i++) { + result = hash_get(clone, keys[i]); + if (result == NULL) { + /* lookup failed! */ + return 1; + } + if (strcmp(values[i], result)) { + /* lookup returned incorrect value! */ + return 1; + } + } + + /* test key iterator */ + iter = hash_iter_new(clone); + if (iter == NULL) { + /* iterator allocation failed! */ + return 1; + } + for (i = 0; i < nkeys; i++) { + key = hash_iter_next(iter); + printf("key: '%s'\n", key); + } + key = hash_iter_next(iter); + if (key != NULL) { + /* extra keys returned! */ + return 1; + } + key = hash_iter_next(iter); + if (key != NULL) { + /* extra keys returned! */ + return 1; + } + hash_iter_release(iter); + + /* release the hash table */ + hash_release(table); + + /* test drops */ + hash_drop(clone, keys[2]); + if (hash_get(clone, keys[2]) != NULL) return 1; + hash_drop(clone, keys[1]); + hash_drop(clone, keys[4]); + if (hash_get(clone, keys[4]) != NULL) return 1; + if (hash_get(clone, keys[1]) != NULL) return 1; + /* keys 0,3 should still be available */ + if (hash_get(clone, keys[0]) == NULL) return 1; + if (hash_get(clone, keys[3]) == NULL) return 1; + + /* release our clone */ + hash_release(clone); + + /* release our library context */ + xmpp_ctx_free(ctx); + + return err; +} diff --git a/tests/test_jid.c b/tests/test_jid.c new file mode 100644 index 0000000..9ac170d --- /dev/null +++ b/tests/test_jid.c @@ -0,0 +1,128 @@ +/* test_base64.c +** XMPP client library -- test routines for the jid utils +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include + +#include "xmpp.h" +#include "common.h" + +static const char jid1[] = "foo@bar.com"; +static const char jid2[] = "anyone@example.com/hullo"; +static const char jid3[] = "manic.porter@xyz.net/frob"; +static const char jid4[] = "domain.tld"; + +int test_jid(xmpp_ctx_t *ctx) +{ + char *node; + char *domain; + char *resource; + + node = xmpp_jid_node(ctx, jid1); + domain = xmpp_jid_domain(ctx, jid1); + resource = xmpp_jid_resource(ctx, jid1); + printf("jid '%s' parsed to %s, %s, %s\n", + jid1, node, domain, resource); + if (strcmp(node, "foo")) return 1; + if (strcmp(domain, "bar.com")) return 1; + if (resource != NULL) return 1; + if (node) xmpp_free(ctx, node); + if (domain) xmpp_free(ctx, domain); + if (resource) xmpp_free(ctx, resource); + + node = xmpp_jid_node(ctx, jid2); + domain = xmpp_jid_domain(ctx, jid2); + resource = xmpp_jid_resource(ctx, jid2); + printf("jid '%s' parsed to %s, %s, %s\n", + jid2, node, domain, resource); + if (strcmp(node, "anyone")) return 1; + if (strcmp(domain, "example.com")) return 1; + if (strcmp(resource, "hullo")) return 1; + if (node) xmpp_free(ctx, node); + if (domain) xmpp_free(ctx, domain); + if (resource) xmpp_free(ctx, resource); + + node = xmpp_jid_node(ctx, jid3); + domain = xmpp_jid_domain(ctx, jid3); + resource = xmpp_jid_resource(ctx, jid3); + printf("jid '%s' parsed to %s, %s, %s\n", + jid3, node, domain, resource); + if (strcmp(node, "manic.porter")) return 1; + if (strcmp(domain, "xyz.net")) return 1; + if (strcmp(resource, "frob")) return 1; + if (node) xmpp_free(ctx, node); + if (domain) xmpp_free(ctx, domain); + if (resource) xmpp_free(ctx, resource); + + node = xmpp_jid_node(ctx, jid4); + domain = xmpp_jid_domain(ctx, jid4); + resource = xmpp_jid_resource(ctx, jid4); + printf("jid '%s' parsed to %s, %s, %s\n", + jid4, node, domain, resource); + if (node != NULL) return 1; + if (strcmp(domain, "domain.tld")) return 1; + if (resource != NULL) return 1; + if (node) xmpp_free(ctx, node); + if (domain) xmpp_free(ctx, domain); + if (resource) xmpp_free(ctx, resource); + + return 0; +} + +int test_jid_new(xmpp_ctx_t *ctx) +{ + char *jid; + + jid = xmpp_jid_new(ctx, "node", "domain", "resource"); + printf("new jid: '%s'\n", jid); + if (strcmp(jid, "node@domain/resource")) return 1; + xmpp_free(ctx, jid); + + jid = xmpp_jid_new(ctx, "foo", "bar.com", NULL); + printf("new jid: '%s'\n", jid); + if (strcmp(jid, "foo@bar.com")) return 1; + xmpp_free(ctx, jid); + + return 0; +} + +int main(int argc, char *argv[]) +{ + xmpp_ctx_t *ctx; + int ret; + + printf("allocating context... "); + ctx = xmpp_ctx_new(NULL, NULL); + if (ctx == NULL) printf("failed to create context\n"); + if (ctx == NULL) return -1; + printf("ok.\n"); + + printf("testing jid routines... "); + ret = test_jid(ctx); + if (ret) printf("failed!\n"); + if (ret) return ret; + printf("ok.\n"); + + printf("testing jid new routines... "); + ret = test_jid_new(ctx); + if (ret) printf("failed!\n"); + if (ret) return ret; + printf("ok.\n"); + + printf("freeing context... "); + xmpp_ctx_free(ctx); + printf("ok.\n"); + + return ret; +} diff --git a/tests/test_sasl.c b/tests/test_sasl.c new file mode 100644 index 0000000..a500437 --- /dev/null +++ b/tests/test_sasl.c @@ -0,0 +1,114 @@ +/* test_sasl.c +** XMPP client library -- test routines for the SASL implementation +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include + +#include "xmpp.h" +#include "common.h" +#include "sasl.h" + +static const unsigned char jid[] = "foo@bar.com"; +static const char password[] = "secret"; +static const char response_plain[] = "AGZvb0BiYXIuY29tAHNlY3JldA=="; + +static const char challenge_md5[] = + "cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgi" + "LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg=="; +static const char response_md5[] = + "dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0i" + "T0E2TUc5dEVRR20yaGgiLGNub25jZT0iMDBERUFEQkVFRjAwIixuYz0wMDAw" + "MDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL3NvbWVyZWFsbSIscmVz" + "cG9uc2U9NGVhNmU4N2JjMDkzMzUwNzQzZGIyOGQ3MDIwOGNhZmIsY2hhcnNl" + "dD11dGYtOA=="; +static const char response_md5_rfc[] = + "dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0i" + "T0E2TUc5dEVRR20yaGgiLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLG5jPTAw" + "MDAwMDAxLHFvcD1hdXRoLGRpZ2VzdC11cmk9InhtcHAvZXhhbXBsZS5jb20i" + "LHJlc3BvbnNlPWQzODhkYWQ5MGQ0YmJkNzYwYTE1MjMyMWYyMTQzYWY3LGNo" + "YXJzZXQ9dXRmLTgK"; + +static const char challenge_md5_2[] = + "cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0" + "RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hh" + "cnNldD11dGYtOA=="; +static const char response_md5_2[] = + "Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2" + "QuaW5ub3NvZnQuY29tIixub25jZT0iT0E2TUc5dEVRR20yaGgiLG5jPTAw" + "MDAwMDAxLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLGRpZ2VzdC11cmk9Im" + "ltYXAvZWx3b29kLmlubm9zb2Z0LmNvbSIscmVzcG9uc2U9ZDM4OGRhZDkw" + "ZDRiYmQ3NjBhMTUyMzIxZjIxNDNhZjcscW9wPWF1dGg="; + +int test_plain(xmpp_ctx_t *ctx) +{ + char *result; + + result = sasl_plain(ctx, jid, password); + if (result == NULL) { + /* SASL PLAIN internal failure! */ + return 1; + } + if (strncmp(response_plain, result, strlen(response_plain))) { + /* SASL PLAIN returned incorrect string! */ + return 2; + } + xmpp_free(ctx, result); + + return 0; +} + +int test_digest_md5(xmpp_ctx_t *ctx) +{ + char *result; + + result = sasl_digest_md5(ctx, challenge_md5, + "somenode@somerealm", "secret"); + printf("response:\n%s\n", result); + if (strcmp(response_md5, result)) { + /* generated incorrect response to challenge */ + return 1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + xmpp_ctx_t *ctx; + int ret; + + printf("allocating context... "); + ctx = xmpp_ctx_new(NULL, NULL); + if (ctx == NULL) printf("failed to create context\n"); + if (ctx == NULL) return -1; + printf("ok.\n"); + + printf("testing SASL PLAIN... "); + ret = test_plain(ctx); + if (ret) printf("failed!\n"); + if (ret) return ret; + printf("ok.\n"); + + printf("testing SASL DIGEST-MD5... "); + ret = test_digest_md5(ctx); + if (ret) printf("failed!\n"); + if (ret) return ret; + printf("ok.\n"); + + printf("freeing context... "); + xmpp_ctx_free(ctx); + printf("ok.\n"); + + return ret; +} diff --git a/tests/test_sock.c b/tests/test_sock.c new file mode 100644 index 0000000..648196b --- /dev/null +++ b/tests/test_sock.c @@ -0,0 +1,64 @@ +/* test_sock.c +** XMPP client library -- test routines for the socket abstraction +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include +#include + +#include "sock.h" + +int wait_for_connect(sock_t sock) +{ + fd_set wfds, efds; + int ret; + + FD_ZERO(&wfds); FD_SET(sock, &wfds); + FD_ZERO(&efds); FD_SET(sock, &efds); + + ret = select(sock + 1, NULL, &wfds, &efds, NULL); + if (ret <= 0) return -1; + + if (FD_ISSET(sock, &efds)) return 0; + if (FD_ISSET(sock, &wfds)) return 1; + + return -1; +} + +int main(int argc, char **argv) +{ + sock_t sock; + int err; + + sock_initialize(); + + sock = sock_connect("www.google.com", 80); + + if (sock < 0) { + sock_shutdown(); + return 1; + } + + err = wait_for_connect(sock); + if (err < 0) { + sock_close(sock); + sock_shutdown(); + return 1; + } + + sock_close(sock); + + sock_shutdown(); + + return 0; +} diff --git a/xmpp.h b/xmpp.h new file mode 100644 index 0000000..e7c0ffa --- /dev/null +++ b/xmpp.h @@ -0,0 +1,261 @@ +/* xmpp.h +** XMPP client library API +** +** Copyright (C) 2005 OGG, LLC. All rights reserved. +*/ + +#ifndef __LIBXMPP_XMPP_H__ +#define __LIBXMPP_XMPP_H__ + +#include + +/* namespace defines */ +#define XMPP_NS_CLIENT "jabber:client" +#define XMPP_NS_COMPONENT "jabber:component:accept" +#define XMPP_NS_STREAMS "http://etherx.jabber.org/streams" +#define XMPP_NS_TLS "urn:ietf:params:xml:ns:xmpp-tls" +#define XMPP_NS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define XMPP_NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define XMPP_NS_SESSION "urn:ietf:params:xml:ns:xmpp-session" +#define XMPP_NS_AUTH "jabber:iq:auth" +#define XMPP_NS_DISCO_INFO "http://jabber.org/protocol/disco#info" +#define XMPP_NS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" +#define XMPP_NS_ROSTER "jabber:iq:roster" + +/** version **/ + +int xmpp_version_check(int major, int minor); + +/** run-time contexts **/ + +/* user-replaceable memory allocator */ +typedef struct _xmpp_mem_t xmpp_mem_t; + +/* user-replaceable log object */ +typedef struct _xmpp_log_t xmpp_log_t; + +/* opaque run time context containing the above hooks */ +typedef struct _xmpp_ctx_t xmpp_ctx_t; + +xmpp_ctx_t *xmpp_ctx_new(const xmpp_mem_t * const mem, + const xmpp_log_t * const log); +void xmpp_ctx_free(xmpp_ctx_t * const ctx); + +struct _xmpp_mem_t { + void *(*alloc)(const size_t size); + void (*free)(void *p); + void *(*realloc)(void *p, const size_t size); +}; + +typedef enum { + XMPP_LEVEL_DEBUG, + XMPP_LEVEL_INFO, + XMPP_LEVEL_WARN, + XMPP_LEVEL_ERROR +} xmpp_log_level_t; + +typedef enum { + XMPP_UNKNOWN, + XMPP_CLIENT, + XMPP_COMPONENT +} xmpp_conn_type_t; + +typedef void (*xmpp_log_handler)(void * const userdata, + const xmpp_log_level_t level, + const char * const area, + const char * const msg); + +struct _xmpp_log_t { + xmpp_log_handler handler; + void * userdata; + /* mutex_t lock; */ +}; + +/** connection **/ + +/* opaque connection object */ +typedef struct _xmpp_conn_t xmpp_conn_t; +typedef struct _xmpp_stanza_t xmpp_stanza_t; + +/* connect callback */ +typedef enum { + XMPP_CONN_CONNECT, + XMPP_CONN_DISCONNECT +} xmpp_conn_event_t; + +typedef enum { + XMPP_SE_BAD_FORMAT, + XMPP_SE_BAD_NS_PREFIX, + XMPP_SE_CONFLICT, + XMPP_SE_CONN_TIMEOUT, + XMPP_SE_HOST_GONE, + XMPP_SE_HOST_UNKNOWN, + XMPP_SE_IMPROPER_ADDR, + XMPP_SE_INTERNAL_SERVER_ERROR, + XMPP_SE_INVALID_FROM, + XMPP_SE_INVALID_ID, + XMPP_SE_INVALID_NS, + XMPP_SE_INVALID_XML, + XMPP_SE_NOT_AUTHORIZED, + XMPP_SE_POLICY_VIOLATION, + XMPP_SE_REMOTE_CONN_FAILED, + XMPP_SE_RESOURCE_CONSTRAINT, + XMPP_SE_RESTRICTED_XML, + XMPP_SE_SEE_OTHER_HOST, + XMPP_SE_SYSTEM_SHUTDOWN, + XMPP_SE_UNDEFINED_CONDITION, + XMPP_SE_UNSUPPORTED_ENCODING, + XMPP_SE_UNSUPPORTED_STANZA_TYPE, + XMPP_SE_UNSUPPORTED_VERSION, + XMPP_SE_XML_NOT_WELL_FORMED +} xmpp_error_type_t; + +typedef struct { + xmpp_error_type_t type; + char *text; + xmpp_stanza_t *stanza; +} xmpp_stream_error_t; + +typedef void (*xmpp_conn_handler)(xmpp_conn_t * const conn, + const xmpp_conn_event_t event, + const int error, + xmpp_stream_error_t * const stream_error, + void * const userdata); + +xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t * const ctx); +xmpp_conn_t * xmpp_conn_clone(xmpp_conn_t * const conn); +void xmpp_conn_release(xmpp_conn_t * const conn); + +const char *xmpp_conn_get_jid(const xmpp_conn_t * const conn); +void xmpp_conn_set_jid(xmpp_conn_t * const conn, const char * const jid); +const char *xmpp_conn_get_pass(const xmpp_conn_t * const conn); +void xmpp_conn_set_pass(xmpp_conn_t * const conn, const char * const pass); + +int xmpp_connect_client(xmpp_conn_t * const conn, + const char * const domain, + xmpp_conn_handler callback, + void * const userdata); + +/* +int xmpp_connect_component(conn, name) +*/ +void xmpp_disconnect(xmpp_conn_t * const conn); + +void xmpp_send(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza); + + +/* handlers */ + +/* if the handle returns false it is removed */ +typedef int (*xmpp_timed_handler)(xmpp_conn_t * const conn, + void * const userdata); + +void xmpp_timed_handler_add(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata); +void xmpp_timed_handler_delete(xmpp_conn_t * const conn, + xmpp_timed_handler handler); + + +/* if the handler returns false it is removed */ +typedef int (*xmpp_handler)(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); + +void xmpp_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata); +void xmpp_handler_delete(xmpp_conn_t * const conn, + xmpp_handler handler); + +void xmpp_id_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata); +void xmpp_id_handler_delete(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id); + +/* +void xmpp_register_stanza_handler(conn, stanza, xmlns, type, handler) +*/ + +/** stanzas **/ + +/** allocate an initialize a blank stanza */ +xmpp_stanza_t *xmpp_stanza_new(xmpp_ctx_t *ctx); + +/** clone a stanza */ +xmpp_stanza_t *xmpp_stanza_clone(xmpp_stanza_t * const stanza); + +/** copies a stanza and all children */ +xmpp_stanza_t * xmpp_stanza_copy(const xmpp_stanza_t * const stanza); + +/** free a stanza object and it's contents */ +void xmpp_stanza_release(xmpp_stanza_t * const stanza); + +/** marshall a stanza into text for transmission or display **/ +int xmpp_stanza_to_text(xmpp_stanza_t *stanza, + char ** const buf, size_t * const buflen); + +xmpp_stanza_t *xmpp_stanza_get_children(xmpp_stanza_t * const stanza); +xmpp_stanza_t *xmpp_stanza_get_child_by_name(xmpp_stanza_t * const stanza, + const char * const name); +xmpp_stanza_t *xmpp_stanza_get_next(xmpp_stanza_t * const stanza); +char *xmpp_stanza_get_attribute(xmpp_stanza_t * const stanza, + const char * const name); +char * xmpp_stanza_get_ns(xmpp_stanza_t * const stanza); +/* concatenate all child text nodes. this function + * returns a string that must be freed by the caller */ +char *xmpp_stanza_get_text(xmpp_stanza_t * const stanza); +char *xmpp_stanza_get_name(xmpp_stanza_t * const stanza); + +void xmpp_stanza_add_child(xmpp_stanza_t *stanza, xmpp_stanza_t *child); +void xmpp_stanza_set_ns(xmpp_stanza_t * const stanza, const char * const ns); +/* set_attribute adds/replaces attributes */ +void xmpp_stanza_set_attribute(xmpp_stanza_t * const stanza, + const char * const key, + const char * const value); +void xmpp_stanza_set_name(xmpp_stanza_t *stanza, + const char * const name); +void xmpp_stanza_set_text(xmpp_stanza_t *stanza, + const char * const text); +void xmpp_stanza_set_text_with_size(xmpp_stanza_t *stanza, + const char * const text, + const size_t size); + +/* common stanza helpers */ +char *xmpp_stanza_get_type(xmpp_stanza_t * const stanza); +char *xmpp_stanza_get_id(xmpp_stanza_t * const stanza); +void xmpp_stanza_get_to(); +void xmpp_stanza_get_from(); +void xmpp_stanza_set_id(xmpp_stanza_t * const stanza, + const char * const id); +void xmpp_stanza_set_type(xmpp_stanza_t * const stanza, + const char * const type); +void xmpp_stanza_set_to(); +void xmpp_stanza_set_from(); + +/** allocate and initialize a stanza in reply to another */ +xmpp_stanza_t *xmpp_stanza_reply(const xmpp_stanza_t *stanza); + +/* stanza subclasses */ +void xmpp_message_new(); +void xmpp_message_get_body(); +void xmpp_message_set_body(); + +void xmpp_iq_new(); +void xmpp_presence_new(); + + +/** event loop **/ +void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout); +void xmpp_run(xmpp_ctx_t *ctx); +void xmpp_stop(xmpp_ctx_t *ctx); + +#endif /* __LIBXMPP_XMPP_H__ */