diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..4dc9c829 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,91 @@ +The main culprits for this release are: + + Ryan Eatmon + - Much of the early work on jadc2s, which fed into the design of SX + and the components. Also the startup script, and some excellent + design work which helped to set the long term goals for the + project (router meshing, distributed components, etc). + + Jeremie Miller + - Getting the project off the ground in the first place. MIO, much + of the SM core (formerly jadlsm), most of the utilties (which came + from the 1.4 server), and of course the NAD subsystem, which is + the boon and the bane of a j2 developers' life ;) + + Thomas Muldowney + - The original Autotools-based build system, some early jadc2s work + (rate limiting), utilities, and lots of good testing and debugging + advice. + + Robert Norris + - Overall system design, pretty much all the other code, and the + current coordinator for this whole mess. + + Stephen Marquard + - Uber bug fixer and the main dev keeping this project alive + +Other people who chipped in: + + Casey Crabb + - Original DB4.1 support + - Original private XML storage module + + Matthias Wimmer + - IPv6 support + - Misc bugfixes and testing + + William Uther + - Base64 passwords for pipe-auth + + Wim Lewis + - SX partial write support + - Numerous fixes for BSD + + Mike Prince + - SQL templates for MySQL/PostgreSQL authreg modules + + Jamin W. Collins + - Command line parsing fixes + + Jacek Konieczny + - Misc bugfixes + + maqi + - Doxygen bootstrapping + + Karsten Huneycutt + - Patch to get PAM working on non-Linux systems + + Shane DeRidder + - Patch for configurable syslog facility + + Magnus Henoch + - Patch for proper router connect retry on OpenBSD + + Dudley Carr + - Patches to bring mod_privacy in line with XMPP-IM + + Patrick Bihan-Faou + - Patch to use PAM account management functions + + Peter Hinz + - Original Win32 port + - Misc bugfixes + + Karsten Petersen + - Misc bugfixes, leak fixes and cleanups + + Etan Reisner + - Patch to configure c2s to require TLS before auth + + Albert Chin + - Various fixes to get things running on Solaris, AIX & Tru64 + +Thanks also go to the testers and bug reporters out there - you know who +you are. Special thanks in this capacity to Justin Kirby, whose many +failed efforts to compile j2 helped get much of the build system to +where it is today. + +If you're have questions or problems, please check the README file for +places you can go for help. Expect to be ignored if you contact the +authors directly. diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..6dad04ae --- /dev/null +++ b/COPYING @@ -0,0 +1,344 @@ + +As a special exception, the authors give permission to link this program +with the OpenSSL library and distribute the resulting binary. + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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/ChangeLog b/ChangeLog new file mode 100644 index 00000000..6042d9c4 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2779 @@ +2005-10-02 Justin Kirby + * fixed SASL anonymous, bug#126 +2005-09-27 Justin Kirby + * fixed edge cases with new dynamic jid code +2005-09-20 Justin Kirby + * fixed incorrect free order in c2s, byg#125 + * corrected debug logging, bug#119 +2005-09-09 Justin Kirby + * fixed s2s bus error on 64-bit architectures, bug#122 + * fixed c2s collisions due to long jids, bug#118 + * fixed error response to iq result, bug#110 + * fixed roster pushing packets without id, bug#73 + * updated dynamic jid patch to stable version, bug#100 +2005-08-25 Justin Kirby + * fixed double free of nad in c2s and s2s, bug#97 +2005-08-21 Justin Kirby + * added check in configure for db43 and db41, bug#105 + * major memory enhancement, made jid structure dynamically allocated, bug#100 +2005-08-17 Justin Kirby + * modules in sm are now dynamic libs, bug#103 + * fixed glibc error with custom sql statements, bug#106 + * fixed segfault with keepalives, bug#102 +2005-08-07 Justin Kirby + * replaced scod with cyrus sasl, bug#45 +2005-06-22 Justin Kirby + * fixed sx io mem leak, bug#90 + * fixed c2s glibc abort and mysql option flags, bug#82 + * fixed version attribute reply in stream, bug#94 + * Support verification of SSL certificates for c2s and s2s, bug#56 +2005-06-02 Justin Kirby + * fixed only one user is loaded correctly for each router acl, bug#60 + * fixed s2s segfault under particular connection timeout conditions, bug#66 + * fixed id is being case sensitive, bug#68 + * fixed Users cannot login after a long period of server inactivity, bug#69 + * fixed handling of stream errors, bug#79 +2005-04-20 Justin Kirby + * fixed s2s segfault from resolver race condition bug#59 + * fixed mem leak in s2s bug#59 +2005-04-14 Justin Kirby + * added oracle support. + * add keepalives to router bug#54 + * fixed info in PROTOCOL file + * Fixed handling of (un)subscribe packets for XMPP-IM compliance +2005-04-07 Justin Kirby + * fixed time values which were not stored correctly + * the '>' must always be escaped (andrey) + * fixed failed access_check() in hpux-ia64 + * fixed ssl memory leak + * fixed s2s segfaults relating firewall problems +2005-03-31 Justin Kirby + * Memory cleanups in shutdown + * Fixed mysql varchar and options in storage + +2005-03-22 Justin Kirby + * Outgoing presence probe was dropped via privacy lists. Fixed + * Compiler problems with strict aliasing fixed + * Fixed expat.h for non-386 machines + * Disconnected log sink no longer causes crash + * Fixed cases where dns resolution fails, s2s does not remove the domain from theoutgoing queue hash. + * Where an outgoing domain is inactive (no active connection), s2s does not check whether there are packets queued before attempting to bounce queued packets (and adding a debug notice) +2005-01-07 Justin Kirby + * Reset router reconnection counter after successful reconnect + * Correctly close sm sessions held by components which do not close down cleanly + * Remove bdb environment if no longer needed at shutdown + * Fixes error in parameter to storage_match() introduced with patch #51 / 2.0s5 + * End all sessions properly when sm shuts down + * Add check for XMPP-compliant element for stream error descriptions + * Fixes error in patch 57k above (uninitialised variable causing dialback timeout check not to work correctly) + * Add yet another timeout check to s2s for even more resilience when connecting to old & flaky jabber servers ... + * Fix consistency of s2s log entries + * Perform LDAP rebind if given a referral by Active Directory LDAP server + * Resolve -lsocket detection for building on Solaris + +2004-12-11 Justin Kirby + * Fix base64 encoding length in authreg_pipe.c Stephen Marquard, Diagnosed by Jerome Vandenabeele + * Fixes segfault on s2s startup on some platforms when ssl is enabled (local pemfile defined in s2s.xml), Stephen Marquard + * mod_offline handling of jabber:x:event client requests (JEP-0022) can lead to a loop repeatedly adding duplicates to the offline queue under certain race conditions. Correctly detect jabber:x:event notifications and do not respond to them as requests, Stephen Marquard + * Check for invalid jids in directed presence packets, Stephen Marquard, Based on bug report by Christopher Zorn + * Fixes minor memory leaks in authreg_ldap, Ilja Booij + * Fixes error in storage filter code using bdb storage causing sm crash, Stephen Marquard + * Changes incorrectly indexed primary keys to non-unique indexes, adds other indexes for efficiency, and changes type of xml field to increase max allowed length, Stephen Marquard + * Include sys/types.h if available in util.h inter alia for FreeBSD, Stephen Marquard + * Minor code cleanups for compilation on HP-UX, Christof Meerwald + * Fix configure.in for correct handling of resolv.h, Magnus Henoch + * Include resquery checks from MAIN cvs branch in 2.0, Christof Meerwald + * Allows jabberd to start new components and place itself in the background, Richard Bullington-McGuire (original ver), Additional components defined in jabberd.cfg get started as long as they are in the same directory as the jabberd script (useful for mu-conference installed through jcr) The script can daemonize itself with the "-b" switch after starting the various programs it watches over, unless the debug option is set. + * Paranoia, ensure than srv->name is nul terminated., Jedi/Sector One + +2004-11-25 Justin Kirby + * Remove incorrect semicolumn from os_object_free() in sm/object.c Typographical error in code - could lead to memory not being freed correctly - jedi + * Fixes to mysql storage for boundary conditions Apply if using mysql storage. -jedi + * Fix length-related issues in base64 decoding routines. Could affect authentication in c2s. - Christof Meerwald + * Fixes to storage_db.c to avoid roster corruption: "sm/storage_db inserts items in the filter hash table with keys which are located on the stack. This creates confusion when the code later tries to compare with these keys.". - Martin Forssen + * Fixes bug in _nad_escape() where escaping ]]> can cause a segfault when handling large messages where nad_realloc is called. - Stephen Marquard + * Fixes to pgsql storage for boundary conditions and incorrect buffer length calculation - jedi + * Fix minor memory leaks in digest-md5 authentication and nad_free() - Martin Forssen + * Fixes omission of namespace declaration where a namespace has already been used in the XML stanza - Stephen Marquard + * Fixes omission of prefix on attributes processed by nad_parse (e.g. in queue storage) - Stephen Marquard + * Corrects check for deleting previously published disco items from "delete" to "remove" (as per JEP-0030). - Stephen Marquard + * Alters filter handling and adds mysql/pgsql escaping on filter strings to allow brackets and apostrophes in resource names that form part of JIDs stored as roster entries - Stephen Marquard + * Fixes buffer overflow that can lead to segfault in c2s mysql and pgsql auth modules - see report by icbm (www.venustech.com.cn) - Stephen Marquard + * Avoid crash in some versions of FreeBSD / DragonFlyBSD When a TCP socket is accepted and the immediately closed, the client address is not filled, but accept() does return a descriptor. It can be triggered with nmap -sT. The type of the length of a socket is also socklen_t, not size_t. - jedi + * Resolves problems with strings starting "NAD" being interpreted as xml in mysql/postgresql storage Does not require any db changes to implement. - Stephen Marquard + * Updates to etc/c2s.xml.dist.in and etc/s2s.xml.dist.in for c2s certificate chains and s2s ssl connections (patches #25b and #17). - Stephen Marquard + * Corrects pipe_authreg to correctly base64-encode passwords for SET-PASSWORD and CHECK-PASSWORD calls. Apply if using pipe authentication. Note that this will change the previous (incorrect) behaviour for SET-PASSWORD and CHECK-PASSWORD which was to pass the password unencoded. - Stephen Marquard (from dgbbk) + * Correctly free storage instance before c2s exit in cases where storage driver fails to initialise - Cameron Moore + * Adds SSL support to s2s (server-server) connections. Add a entry in the section of s2s.xml to enable. - Stephen Marquard + * tons of autoconf magic fixes by jedi + +2004-10-23 Justin Kirby + * Fixed race condition allowing c2s to be killed, Stephen Marquard + * Fixed off-by-one bug in s2s/main.c leading to segfault on startup in some environmentsp + * Fixed memory leak in sm, Michal Kára + * Fixed problem relating to SSL connections not being closed correctly, Nathan Christiansen + * Fixed 3 problems in mod_announce: (a) NAD freed before use, (b) struct tm not initialised correctly on some platforms, and (c) time not initialised for broadcast motd messages delivered to online users, Stephen Marquard + * Fixed insertion of extra namespace in element in some types of messages retrieved from offline queue, which causes a parse error in the router, Matthew Buckett + * Fixed off-by-one bug in PLAIN SASL authentication code. May also resolve a number of other bugs relating to c2s authentication, Robert Theisen + * Fixed return value of jid_new() in pkt.c to avoid sm segfault from dereferencing NULL pointer, triggered by a message with a to JID of the form "@some.server@", Stephen Marquard + * Avoided adding nads to the cache that are created through nad_copy(), Stephen Marquard + * Fixed bug in retrieving hash values, Stephen Marquard + * Improved performance of pool cleanup function, Stephen Marquard + * Corrected handling of EMAIL, TEL and ADR/CTRY elements in vcards for JEP-0054 compliance, Stephen Marquard + * Optimised sm algorithm for announcing presence to skip presence announcements and probes for users on the same server who are not online, Stephen Marquard + * Checked that storage drivers are initialised correctly; if not, abort, Stephen Marquard + * Fixed file descriptor leak in storage_fs + * Allowed c2s to supply a certificate chain to clients, Iain MacDonnell + +2004-08-19 Robert Norris + + * sm/mod_privacy.c: fixed a tiny little edge case crasher + +2004-06-30 Robert Norris + + * util/util.h: include all the headers need for network stuff (ie struct sockaddr_storage) + +2004-06-25 Robert Norris + + * 2.0s3 released + +2004-06-07 Robert Norris + + * sm/mod_iq_private.c: don't match iq:private packets that are trying to be routed elsewhere + +2004-06-01 Robert Norris + + * configure.in: check and include sys/filio.h; define sa_family_t; define broken sockaddr_storage members + * mio/mio.h: same + + * subst/inet_aton.c: match configure defines + * subst/inet_ntop.c: same + * subst/inet_pton.c: same + * util/util_compat.h: same + + * c2s/authreg_mysql.c: use C-style comments + * c2s/authreg_pgsql.c: same + * sm/mod_iq_version.c: same + * sx/sx.c: same + * util/log.c: same + +2004-05-24 Robert Norris + + * configure.in: use fcntl/O_NONBLOCK instead of ioctl(FIONBIO) where possible + * mio/mio.c: same + * mio/mio.h: same + + * c2s/authreg_pgsql.c: fix potential buffer overruns + + * c2s/c2s.c: shutdown if listening sockets can't be opened + * c2s/c2s.h: same + + * util/util.h: removed debug_flag extern, as it clashes with the decl in log.c, and isn't needed anyway + +2004-05-21 Robert Norris + + * configure.in: do global search/replace on include paths + + * c2s/authreg_mysql.c: fix potential buffer overruns + + * subst/snprintf.c: handle %.*s without calling strlen + +2004-05-06 Robert Norris + + * router/router.c: tiny logging fix + +2004-05-05 Robert Norris + + * router/main.c: fix umask ifdef + + * sm/mod_announce.c: cleanup nads as we finish with them + * sm/storage_db.c: same + * sm/storage_mysql.c: same + * sm/storage_pgsql.c: same + + * sm/mod_privacy.c: cleanup lists properly + + * sm/pres.c: probes come from user only; added pres_probe() for upcoming mod_privacy changes + * sm/sm.h: same + + +2004-04-30 Robert Norris + + * c2s/authreg.c: reject register get on password change only (closes #3059) + + * c2s/authreg_pipe.c: make the exec name the first argument (closes #3174) + + * c2s/authreg.c: fixed various leaks, bugs and other small cleanups + * c2s/authreg_mysql.c: same + * c2s/sm.c: same + * scod/mech_plain.c: same + * sm/mod_disco_publish.c: same + * sm/mod_privacy.c: same + * sm/mod_roster.c: same + * sm/object.c: same + * sm/pkt.c: same + * sm/sm.h: same + * sm/storage.c: same + * sm/storage_db.c: same + * sm/storage_fs.c: same + * sm/storage_mysql.c: same + * sm/storage_pgsql.c: same + * sx/sx.c: same + * sx/sx.h: same + * util/jid.c: same + * util/nad.c: same + * util/serial.c: same + * util/sha1.c: same + * util/sha1.h: same + * util/util.h: same + * util/xdata.c: same + +2004-04-27 Robert Norris + + * sm/aci.c: allow resources in acis + +2004-04-26 Robert Norris + + * expat/*: upgraded to 1.95.7 + + * c2s/authreg_ldap.c: LDAPv3 support (closes #3344) + * etc/c2s.xml.dist.in: same + + * c2s/authreg.c: buffer overrun fix + + * mio/mio.c: only call ACT() for action_WRITE if the socket is in type_NORMAL + +2004-04-25 Robert Norris + + * c2s/c2s.c: added "replaced" session command to support session replacement + * sm/sess.c: same + + * s2s/main.c: generate random dialback key if none provided + * etc/s2s.xml.dist.in: + + * sm/aci.c: cleanup acls on exit + * sm/main.c: same + + * sm/mod_roster.c: removed redundant code + + * sx/error.c: get error lengths right + + * sx/ssl.c: only read ssl data if the encrypted channel is up + + * util/nad.c: free nad_parse() parser when finished + +2004-04-20 Robert Norris + + * util/nad.c: don't explicitly declare the implicit xml namespace + * util/util.h: same + +2004-04-17 Robert Norris + + * mio/mio.c: actually set sockets non-blocking + +2004-04-16 Robert Norris + + * COPYING: added exception to allow linking with openssl + + * configure.in: enable switches for anon authreg and fs storage + * c2s/authreg.c: same + * c2s/authreg_anon.c: same + * sm/storage_fs.c: same + + * resolver/dns.c: win32 bugfixes + * sm/mod_iq_version.c: same + +2004-04-15 Robert Norris + + * configure.in: treat pipe auth as an external package + * c2s/authreg.c: same + * c2s/authreg_pipe.c: same + + * c2s/c2s.h: only include things if they're available + * mio/mio.h: same + * mio/mio_poll.h: same + * mio/mio_select.h: same + * resolver/dns.h: same + * resolver/resolver.h: same + * router/router.h: same + * s2s/s2s.h: same + * sm/mod_iq_version.c: same + * sm/storage.c: same + * sm/storage_fs.c: same + * util/inaddr.h: same + * util/util.h: same + + * mio/mio.c: include fixups + * scod/mech_digest_md5.c: same + * subst/gettimeofday.c: same + * sx/callback.c: same + * sx/chain.c: same + * sx/client.c: same + * sx/env.c: same + * sx/error.c: same + * sx/io.c: same + * sx/sasl.c: same + * sx/sasl.h: same + * sx/server.c: same + * sx/ssl.c: same + * sx/ssl.h: same + * sx/sx.c: same + * sx/sx.h: same + * util/util_compat.h: same + + * c2s/main.c: replace win32 checks with feature checks + * router/main.c: same + * s2s/main.c: same + * sm/main.c: same + + * resolver/dns.c: win32 resolver + * resolver/resolver.h: same + * resolver/resolver.c: same + + * sm/mod_iq_time.c: use normal time functions rather than less-common thread-safe variants + * sm/sm.c: same + + * subst/ip6_misc.h: general cleanups + * subst/syslog.c: same + * util/inaddr.c: same + + * subst/subst.h: more prototypes + +2004-04-14 Robert Norris + + * configure.in: make idn/ssl optional; loads more specific checks + + * c2s/authreg.c: make idn/ssl optional + * c2s/c2s.c: same + * c2s/main.c: same + * resolver/resolver.c: same + * router/main.c: same + * router/router.c: same + * s2s/main.c: same + * s2s/router.c: same + * sm/main.c: same + * sm/sm.c: same + * sx/sasl.c: same + * sx/ssl.c: same + * sx/ssl.h: same + * util/jid.c: same + + * subst/*: compat stuff + + * util/util.h: pull in substitutions + + * win32/: removed + +2004-04-08 Robert Norris + + * idn/*: removed due to license issues + + * configure.in: new build system + * acinclude.m4: same + * Makefile.am: same + * */Makefile.am: same + + * c2s/authreg*.c: updated for new defines + * sm/storage*.c: same + + * c2s/c2s.h: only include required headers + * mio/mio.h: same + * resolver/dns.h: same + * resolver/resolver.h: same + * router/router.h: same + * s2s/s2s.h: same + * sm/sm.h: same + * sx/sx.h: same + * util/inaddr.h: same + * util/util.h: same + + * mio/mio.c: use ioctl instead of fcntl to set non-blocking + + * sx/ssl.c: slightly more standard way to startup openssl + + * util/jid.c: use external stringprep headers + * c2s/authreg.c: same + + * sx/error.c: fix off-by-one bug (closes #3481) + + * util/inaddr.c: use correct size for sin6_len (closes #3594) + + * c2s/authreg.c: get namespace before searching for elements (closes #3368) + + * c2s/main.c: win32 stuff + * expat/Makefile.am: same + * expat/expat_config.h: same + * mio/mio_select.h: same + * resolver/resolver.c: same + * router/main.c: same + * s2s/main.c: same + * sm/main.c: same + * sm/mod_iq_version.c: same + + * win32/*: various interim win32 changes + +2004-04-02 Robert Norris + + * sm/mod_presence.c: block incoming presence if user not online + * etc/sm.xml.dist.in: same + +2004-03-24 Robert Norris + + * win32/*: initial checkin of win32 compatibility files + + * configure.in: basic mingw checks + + * acinclude.m4: helper macros moved here to reduce clutter + + * ac-helpers/*: removed + + * c2s/c2s.c: moved from read/write to recv/send + * resolver/resolver.c: same + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * sm/sm.c: same + +2004-03-23 Robert Norris + + * sx/sasl.c: optionally require tls before auth + * sx/sasl.h: same + * sx/ssl.c: same + * c2s/c2s.c: same + * c2s/c2s.h: same + * c2s/main.c: same + * sx/ssl.h: same + * etc/c2s.xml.dist.in: same + + * sm/main.c: free stream environment after closing the last stream + +2004-03-22 Robert Norris + + * sx/io.c: thinko fix + +2004-03-19 Robert Norris + + * sx/io.c: don't close streams that are already closing + + * router/router.c: initialize prep cache for local jid structures (closes #3480) + + * sm/main.c: free the router stream (and nad cache) after all nads are freed + + * sm/pkt.c: memory leak fixes + * sm/storage_db.c: same + + * util/nad.c: prevent running of the end of the element list during print + + * c2s/bind.c: present correct session namespace to xmpp clients + * c2s/c2s.c: same + * util/util.h: same + +2004-02-26 Robert Norris + + * util/util.h: make the debug line buffer larger so long lines don't get cut + +2004-02-17 Robert Norris + + * c2s/authreg.c: don't call the module free function if it doesn't exist + +2004-02-13 Robert Norris + + * c2s/authreg_ldap.c: logging typo fix (closes #3343) + +2004-02-10 Robert Norris + + * ac-helpers/berkeley-db.m4: removed -R flags from link directives + * ac-helpers/ldap.m4: same + * ac-helpers/mysql.m4: same + * ac-helpers/openssl.m4: same + * ac-helpers/pam.m4: same + * ac-helpers/pgsql.m4: same + + * ac-helpers/ldap.m4: build against openldap > api 2004 + + * 2.0s2 released + +2004-02-03 Robert Norris + + * sx/io.c: new function sx_raw_write(), send raw data over a stream + * sx/sx.h: same + + * c2s/main.c: use sx_raw_write() to send keepalives (closes #3144) + * c2s/c2s.c: same + * c2s/c2s.h: same + * s2s/main.c: same + * s2s/out.c: same + * s2s/s2s.h: same + + * sm/pkt.c: treat empty packet addresses as though they don't exist + + * configure.in: check for vsyslog() + + * util/log.c: use vsnprintf() & syslog() to replace vsyslog() on systems that don't have it + + * sm/mod_roster.c: gracefully handle malformed roster jids stored in the database (closes #3157) + +2004-02-02 Robert Norris + + * sm/mod_privacy.c: relink item list correctly + +2004-01-29 Robert Norris + + * sm/pres.c: always forward broadcasts to trusted jids + +2004-01-27 Robert Norris + + * sm/mod_privacy.c: order items properly; send "subscription" instead of "s10n" + +2004-01-23 Robert Norris + + * sm/mod_privacy.c: match group jids correctly + + * sm/mm.c: silence warnings + + * configure.in: look for DB 4.2 + +2004-01-22 Robert Norris + + * sx/io.c: make sx_kill() shut things down properly + +2004-01-21 Robert Norris + + * c2s/authreg_pam.c: use pam account management functions + + * sx/io.c: new function sx_kill(), to close a stream abruptly + * sx/sx.h: same + + * c2s/c2s.c: use sx_kill() to close streams after io errors + * resolver/resolver.c: same + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * sm/sm.c: same + +2004-01-20 Robert Norris + + * sm/mod_privacy.c: match item jids correctly + + * c2s/authreg.c: brought registration into line with jep-0077 (closes #3166) + +2004-01-19 Robert Norris + + * sm/mod_privacy.c: brought in line with latest xmpp-im + + * scod/mech_digest_md5.c: quote qop list returned to client + +2004-01-03 Robert Norris + + * c2s/authreg_ldap.c: don't allow zero-length passwords (closes #3131) + +2003-12-28 Robert Norris + + * 2.0s1 released + +2003-12-27 Robert Norris + + * configure.in: don't fail configure if inet_ntop() not found (closes #3080) + +2003-12-24 Robert Norris + + * sx/io.c: only send stream close once + +2003-12-17 Robert Norris + + * c2s/main.c: do router connect retry properly on openbsd + * resolver/resolver.c: same + * s2s/main.c: same + * sm/main.c: same + +2003-12-16 Matthias Wimmer + + * sx/sx.h: small syntax fix in _sx_state() macro + +2003-12-15 Robert Norris + + * sx/ssl.c: don't offer tls after auth + + * mio/mio.c: use ipv6 compat functions from util [mawis] + * mio/mio.h: same + * util/inaddr.c: same + * util/inaddr.h: same + * util/util.h: same + * util/util_compat.h: same + + * sm/mod_roster.c: only drop roster results + + * sx/io.c: ignore parse errors that happen after failures + +2003-12-12 Robert Norris + + * router/aci.c: create a new acl for each type + + * sm/mod_privacy: remove default list correctly + +2003-12-11 Matthias Wimmer + + * sm/aci.c: create a new acl for each type + +2003-12-10 Robert Norris + + * s2s/out.c: make sure we actually requested an incoming resolver response + + * 2.0rc2 released + +2003-12-09 Robert Norris + + * sm/mod_roster.c: drop un-namespaced iq results + +2003-12-08 Robert Norris + + * configure.in: check for "broken" snprintf/vsnprintf + * ac-helpers/j2_printf.m4: same + + * configure.in: check for FIONREAD + + * util/ldap.m4: removed unneeded version variables + + * router/aci.c: load multiple names correctly (closes #3026) + + * ac-helpers/mysql.m4: paths for mysql on freebsd (closes #3028) + +2003-12-05 Robert Norris + + * sm/mod_presence.c: don't set namespace on type attribute (closes #3011) + + * sm/pres.c: forward invisible to sessions as unavailable + + * sx/io.c: close stream properly on certain errors + * sx/ssl.c: same + + * sx/sx.c: don't use vfprintf, avoids crashes on solaris + + * ac-helpers/mysql.m4: small log tweak + * ac-helpers/pam.m4: same + * ac-helpers/pgsql.m4: same + + * ac-helpers/ldap.m4: moved from openldap.m4; only link libldap; only check LDAP_API_VERSION (support Sun LDAP) + + * ac-helpers/configure.in: load ldap.m4 + +2003-12-04 Robert Norris + + * sm/pres.c: force presence unavailable and invisible broadcasts to all sessions + + * sx/server.c: only process pre-stream packets if the stream is open (closes #2987) + + * sx/io.c: drop outgoing packets if the stream is closed + +2003-12-03 Robert Norris + + * s2s/main.c: bounce non-client packets + * s2s/out.c: same + + * sm/pres.c: forward presence broadcasts to all user sessions (closes #2993) + + * sm/mod_roster.c: send appropriate unsubscriptions on roster remove (closes #3002) + + * sm/dispatch.c: require from address on sm packets + + * sx/error.c: don't send the null over the wire + +2003-12-02 Robert Norris + + * c2s/c2s.c: watch for closed states on file descriptors + * resolver/resolver.c: same + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * sm/sm.c: same + * util/util.h: same + + * c2s/bind.c: offer xmpp-session feature + + * sm/mod_announce.c: small memory leak fixes + +2003-12-01 Robert Norris + + * sm/mod_deliver.c: process packets with unmatched resources correctly + + * s2s/out.c: don't error non-client packets + +2003-11-28 Robert Norris + + * sm/mod_disco.c: force gateways into the correct browse category + +2003-11-27 Robert Norris + + * c2s/c2s.c: pre-stream race fix + + * c2s/bind.c: doxygen updates + * mio/mio.h: same + * mio/mio_inaddr.h: same + * sm/sm.h: same + * util/inaddr.c: same + * util/util_compat.h: same + +2003-11-26 Robert Norris + + * sx/server.c: only allow "stream" as stream element name (closes #2962) + * sx/client.c: same + + * sm/storage_db.c: handle empty objects correctly + * sm/storage_mysql.c: same + * sm/storage_pgsql.c: same + +2003-11-25 Robert Norris + + * c2s/authreg.c: better session start logging + +2003-11-21 Robert Norris + + * sm/dispatch.c: free users that were only loaded for delivery + +2003-11-20 Matthias Wimmer + + * configure.in: POOL_DEBUG got defined with --enable-nad-debug + +2003-11-17 Robert Norris + + * 2.0rc1 released + +2003-11-11 Robert Norris + + * configure.in: default authreg is "mysql" + + * util/log.c: only compile in debug logging code when debug is enabled + + * util/pool.c: print directly rather than using debug_log() + + * c2s/main.c: provide regular pool stats when running in pool debug mode + * resolver/resolver.c: same + * router/main.c: same + * s2s/main.c: same + * sm/main.c: same + + * c2s/main.c: only free sessions once at shutdown + + * resolver/dns.h: added bind 8 compat define for os x 10.3 + + * sm/mod_iq_vcard.c: route packets with to addresses, rather than handling implicitly + +2003-11-10 Robert Norris + + * configure.in: configure option for pool debugging + * util/pool.c: same + * util/util.h: same + +2003-11-09 Matthias Wimmer + + * c2s/main.c: calling pool debugging at the end + * resolver/main.c: same + * router/main.c: same + * s2s/main.c: same + * sm/main.c: same + * util/pool.c: fix the pool debugging functions + * util/util.h: same + +2003-11-04 Robert Norris + + * sx/error.c: don't crash on in-stream errors + + * c2s/c2s.c: ignore pre-auth sessions on offline component checks + +2003-11-03 Robert Norris + + * c2s/authreg.c: use normal stanza errors + + * c2s/c2s.c: close stream on pre-session packets + + * sm/pkt.c: limit presence priority to -128 < p < 127 + + * sx/error.c: updated to latest xmpp; open stream before reporting pre-stream errors + * sx/sx.h: same + + * sx/sasl.c: updated errors to latest xmpp + * util/stanza.c: same + * util/util.h: same + + * sx/server.c: version checking and response + + * sx/client.c: more correct error usage + + * util/nad.c: silence compiler warnings + + * sm/*.c: doxygen comments + + * c2s/authreg_pgsql.c: clear results when finished + * sm/storage_pgsql.c: same + + * etc/sm.xml.dist.in: removed browse compat namespaces + + * Doxyfile.in: updated doxygen config + + * 2.0b3 released + +2003-10-31 Robert Norris + + * sm/mod_roster.c: xmpp-im updates + +2003-10-30 Robert Norris + + * c2s/c2s.c: a note about resource conflicts + + * c2s/sm.c: change session control prefix + * sm/sm.c: same + + * etc/resolver.xml.dist.in: use xmpp-server instead of jabber-server + + * sm/mod_roster.c: must ignore the to address on roster packets from clients + +2003-10-29 Robert Norris + + * sx/error.c: update stream errors to recent xmpp revisions + * sx/sx.h: same + + * util/stanza.c: update stanza errors to recent xmpp revisions + + * c2s/c2s.c: send diagnostic text on stream errors + * c2s/main.c: same + * router/router.c: same + * sx/client.c: same + * sx/io.c: same + * sx/server.c: same + * sx/ssl.c: same + +2003-10-28 Robert Norris + + * c2s/c2s.c: support for xmpp resource binding + * c2s/bind.c: same + * c2s/c2s.h: same + * util/util.h: same + + * scod/mech_digest_md5.c: sasl changes to support recent xmpp revisions + * scod/mech_plain.c: same + * scod/scod.c: same + * sx/sasl.c: same + * c2s/main.c: same + * resolver/resolver.c: same + * router/main.c: same + * s2s/router.c: same + * sm/sm.c: same + +2003-10-27 Robert Norris + + * util/util.h: removed varargs.h test and include + +2003-10-24 Robert Norris + + * c2s/authreg_mysql.c: always allocate templates so they can be freed without issue + * c2s/authreg_pgsql.c: same + + * sm/storage_mysql.c: prefix table names + * sm/storage_pgsql.c: same + + * resolver/resolver.c: logging fixes + +2003-10-23 Robert Norris + + * c2s/c2s.c: c2s won't try to start sessions if the sm isn't online + * c2s/main.c: same + * c2s/c2s.h: same + + * s2s/out.c: check the queue exists before using it + + * util/log.c: optional logging to stdout + * util/util.h: same + * c2s/c2s.h: same + * c2s/main.c: same + * resolver/resolver.c: same + * resolver/resolver.h: same + * router/main.c: same + * router/router.h: same + * s2s/main.c: same + * s2s/s2s.h: same + * sm/main.c: same + * sm/sm.h: same + * etc/c2s.xml.dist.in: same + * etc/resolver.xml.dist.in: same + * etc/router.xml.dist.in: same + * etc/s2s.xml.dist.in: same + * etc/sm.xml.dist.in: same + +2003-10-21 Robert Norris + + * resolver/resolver.c: allow multiple SRV lookups (specified in config) + * resolver/resolver.h: same + * etc/resolver.xml.dist.in: same + + * s2s/main.c: make queue bounces work correctly + * s2s/router.c: same + + * s2s/s2s.c: bounce queue on failed resolution + + * util/xhash.c: xhash_putx returns void, so don't try to take a return value + +2003-10-17 Robert Norris + + * sm/mod_roster.c: use jid_t rather than a static buffer; clear pointers after free + * util/stanza.c: jids buffers are 3072 bytes + +2003-10-16 Robert Norris + + * c2s/c2s.c: handle packets bounced by the sm in a sane way + + * sm/mod_session.c: redundant logging + +2003-10-15 Robert Norris + + * c2s/main.c: actually use the realm once we've computed it + + * c2s/c2s.c: teeny logging update + +2003-10-13 Robert Norris + + * c2s/c2s.c: if c2s or sm disappears unexpectedly, make the other kill corresponding sessions + * sm/mod_session.c: same + * sm/sess.c: same + * sm/sm.h: same + * etc/sm.xml.dist.in: same + + * sm/dispatch.c: don't remove the error attr on bounced packets, so the router can avoid loops properly + + * util/log.c: configurable syslog facility + * util/util.h: same + * c2s/main.c: same + * c2s/c2s.h: same + * resolver/resolver.c: same + * resolver/resolver.h: same + * router/main.c: same + * router/router.h: same + * s2s/main.c: same + * s2s/main.h: same + * sm/main.c: same + * sm/sm.h: same + * etc/c2s.xml.dist.in: same + * etc/resolver.xml.dist.in: same + * etc/router.xml.dist.in: same + * etc/s2s.xml.dist.in: same + * etc/sm.xml.dist.in: same + +2003-10-11 Robert Norris + + * c2s/authreg_pam.c: only use PAM_FAIL_DELAY on systems that support it + +2003-10-08 Robert Norris + + * sm/mod_iq_private.c: don't free the stored object nad + + * resolver/resolver.c: less verbose logging + + * configure.in: configure switch for nad debug + + * 2.0b2 released + +2003-10-07 Robert Norris + + * c2s/authreg_*.c: logging now reports module name + * sm/storage_*.c: same + + * sm/storage_*.c: checks for malformed or missing xml + +2003-10-06 Robert Norris + + * sm/pres.c: slight log text change + + * sm/storage_fs.c: don't read off the end of the buffer + + * sx/server.c: more memory corruption fixes + +2003-10-03 Robert Norris + + * s2s/main.c: don't do checks on unresolved domains + + * tools/migrate.pl: do mysql roster subscriptions properly + +2003-10-02 Robert Norris + + * c2s/authreg_mysql.c: logging updates; handle failed initial connection correctly (closes #2589) + * sm/storage_mysql.c: same + + * c2s/authreg_pgsql.c: check failures properly + * sm/storage_pgsql.c: same + + * sm/mod_offline.c: don't free the stored object nad + + * sm/storage.c: better logging + + * sx/io.c: don't free the nad before we're finished with it + + * util/nad.c: pointer tracking (for debugging) + +2003-09-29 Robert Norris + + * c2s/authreg_pgsql.c: reconnect to database immediately + * sm/storage_pgsql.c: same + +2003-09-27 Matthias Wimmer + + * mio/mio.h: update and add doxygen documentation comments related to IPv6 + * mio/mio_inaddr.c: same (and a small return value fix) + * mio/mio_inaddr.h: same + * util/inaddr.c: same + * util/util_compat.h: same + +2003-09-25 Robert Norris + + * sm/mod_iq_vcard.c: properly set packet to, from and type before returning (closes #2630) + + * ac-helpers/berkeley-db.m4: look in /sw for things (Fink on OS X puts libs here) + * ac-helpers/mysql.m4: same + * ac-helpers/openssl.m4: same + * ac-helpers/pgsql.m4: same + +2003-09-24 Robert Norris + + * scod/mech_digest_md5.c: don't pass pointers to stack variables (closes #2637) + + * c2s/authreg.c: prep checking on iq:auth/iq:register usernames & resources (closes #2638) + + * c2s/main.c: configurable router reconnect + * c2s/c2s.c: same + * c2s/c2s.h: same + * s2s/main.c: same + * s2s/router.c: same + * s2s/s2s.h: same + * etc/c2s.xml.dist.in: same + * etc/s2s.xml.dist.in: same + + * sm/sm.h: documented + +2003-09-23 Robert Norris + + * resolver/resolver.c: configurable router reconnect + * resolver/resolver.h: same + * sm/main.c: same + * sm/sm.c: same + * sm/sm.h: same + * etc/resolver.xml.dist.in: same + * etc/sm.xml.dist.in: same + + * util/daemon.c: removed, not going to do it this way after all + * util/util.h: same + * util/Makefile.am: same + + * c2s/main.c: don't pass pointers to stack variables (closes #2637) + + * sx/io.c: improve sx close semantics + * c2s/c2s.c: same + * resolver/resolver.c: same + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * sm/sm.c: same + +2003-09-22 Robert Norris + + * doxygen.conf: doxygen bootstrapping + * */*.[ch]: same + + * util/daemon.c: daemon utilites + + * c2s/c2s.c: check for null jid before printing it (closes #2639) + +2003-09-19 Robert Norris + + * c2s/c2s.c: check for null jid before printing it (closes #2628) + * scod/mech_digest_md5.c: don't send null after rspauth (closes #2629) + +2003-09-16 Robert Norris + + * sm/mod_disco.c: change category/type to server/im + * etc/sm.xml.dist.in: same + +2003-09-12 Robert Norris + + * tools/migrate.pl: rewritten to use Net::Jabber; migrates auth info + + * sm/mod_announce.c: don't free the stored motd + + * sm/mod_template_roster.c: ask can take three values + + * sm/mod_privacy: fix nasty crasher that hits roster pushes + +2003-09-11 Robert Norris + + * etc/sm.xml.dist.in: updated comments about alternate db drivers + + * util/md5.h: use uint8_t/uint32_t + * util/sha1.h: get rid of typedefs we never use + +2003-09-10 Matthias Wimmer + + * util/util.h: Changed comments that were out of date since revision 1.21 + +2003-09-10 Robert Norris + + * tools/migrate.pl: basic 1.4 migration tool + + * ac-helpers/berkeley-db.m4: more informative error text + * ac-helpers/mysql.m4: same + * ac-helpers/openldap.m4: same + * ac-helpers/openssl.m4: same + * ac-helpers/pam.m4: same + * ac-helpers/pgsql.m4: same + +2003-09-05 Robert Norris + + * etc/sm.xml.dist.in: all vcard requests get answered by the server + +2003-09-04 Robert Norris + + * c2s/main.c: more authzid checks + + * scod/mech_plain.c: buffer overrun fix + + * sm/mod_roster.c: quietly drop roster results, since they're probably push responses + +2003-09-03 Robert Norris + + * sm/pres.c: huge memory leak fix + + * util/datetime.c: gettimeofday needs both arguments + + * c2s/authreg.c: error auth requests if no mechanisms enabled + + * configure.in: tr requires brackets on some systems; check for libsocket and libnsl (needed on solaris) + + * expat/internal.h: only enable optimisations on with gcc and linux (patch from expat cvs) + + * 2.0b1 released + +2003-09-02 Robert Norris + + * configure.in: automake docs says this is a good idea + + * sm/mod_session.c: broke c2s protocol handling out into a module + * sm/dispatch.c: same + * sm/mm.c: same + * etc/sm.xml.dist.in: same + +2003-09-01 Robert Norris + + * scod/mech_anonymous.c: SASL ANONYMOUS support + * scod/scod.c: same + * scod/scod.h: same + * sx/sasl.c: same + * sx/sasl.h: same + + * scod/mech_digest_md5.c: callback prototype change + * scod/mech_plain.c: same + * scod/scod.h: same + * sx/sasl.c: same + * sx/sasl.h: same + + * c2s/c2s.c: updated for ANONYMOUS and sasl callback change + * c2s/c2s.h: same + * c2s/main.c: same + * etc/c2s.xml.dist.in: same + + * sm/sess.c: log update + + * c2s/main.c: initialise random number generator at startup + * resolver/resolver.c: same + * router/main.c: same + * s2s/main.c: same + * sm/main.c: same + + * sx/server.c: don't initalise random number generator + + * c2s/authreg_mysql.c: memory leaks + * c2s/authreg_pgsql.c: same + + * ac-helpers/berkeley-db.m4: include stdio.h after other headers + * ac-helpers/openldap.m4: same + * ac-helpers/openssl.m4: same + +2003-08-29 Robert Norris + + * man/jabberd.8.in: jabberd manpage + + * router/router.c: offer SASL mechanisms + +2003-08-28 Robert Norris + + * sx/io.c: queue pre-stream packets, and replay them after stream establishment + * sx/server.c: same + * sx/sx.h: same + + * c2s/c2s.c: logging tweaks + + * sx/sasl.c: application can specify wanted mechanisms + * sx/sasl.h: same + + * c2s/authreg.c: auth mechanisms restricted by configuration + * c2s/c2s.h: same + * c2s/main.c: same + * etc/c2s.xml.dist.in: same + + * man/*: basic manpages + +2003-08-27 Robert Norris + + * util/jid.c: jid_append() returns list pointer, for new lists + * util/util.h: same + + * sm/aci.c: incorporate jid_append() changes + * sm/pres.c: track A and E lists properly + + * sm/sess.c: full session restart on replacement + + * etc/sm.xml.dist.in: chain cleanups + + * s2s/main.c: bounce queues on timeout, cleanup conns properly + * s2s/in.c: same + * s2s/out.c: same + + * sx/error.c: updated string length to please assertion + + * sm/dispatch.c: only drop broadcasts, not domain unadvertisments + + * sm/mod_disco.c: forward directed agents requests + +2003-08-26 Robert Norris + + * etc/c2s.xml.dist.in: config file comment cleanup + * etc/resolver.xml.dist.in: same + * etc/router.xml.dist.in: same + * etc/s2s.xml.dist.in: same + * etc/sm.xml.dist.in: same + + * s2s/in.c: remove remote starttls support, its not quite right + * s2s/main.c: same + * s2s/s2s.h: same + + * sm/mod_disco.c: proper agents session response (closes #2480) + + * sm/mod_offline.c: actually read queue messages before delivering them + + * sm/mod_roster.c: removed redundant code + + * sm/user.c: don't free users when they've already been freed + + * util/config.c: read attributes properly + + * resolver/dns.c: don't crash on multiple srv records + + * c2s/authreg_mysql.c: sql template updates + * c2s/authreg_pgsql.c: same + +2003-08-25 Robert Norris + + * c2s/authreg.c: optionally allow password changes even if registration is disabled + * c2s/main.c: same + * c2s/c2s.h: same + * etc/c2s.xml.dist.in: same + + * util/Makefile.am: changed queue_* to jqueue_* to help solaris 9 + * util/util.h: same + * util/queue.c: same + * util/jqueue.c: same + * c2s/c2s.c: same + * router/main.c: same + * router/router.c: same + * router/router.h: same + * s2s/in.c: same + * s2s/main.c: same + * s2s/out.c: same + * s2s/s2s.h: same + * sx/callback.c: same + * sx/client.c: same + * sx/error.c: same + * sx/io.c: same + * sx/server.c: same + * sx/ssl.c: same + * sx/ssl.h: same + * sx/sx.c: same + * sx/sx.h: same + +2003-08-20 Matthias Wimmer + + * etc/s2s.xml.dist.in: allow multiple s2s components to be present (needed sometimes for IPv6) + * s2s/main.c: same + * s2s/router.c: same + * s2s/s2s.h: same + +2003-08-20 Robert Norris + + * sm/mod_roster.c: ask can have three values + * tools/db-setup.mysql: same + * tools/db-setup.pgsql: same + +2003-08-14 Robert Norris + + * c2s/main.c: getopt() returns int, not char (closes #2421) + * resolver/resolver.c: same + * router/main.c: same + * s2s/main.c: same + * sm/main.c: same + + * sm/mod_roster.c: generate correctly-namespaced roster packets + + * util/stanza.c: updated error codes to match JEP-0086 v0.2 + +2003-08-13 Robert Norris + + * c2s/main.c: set process umask at startup + * resolver/resolver.c: same + * router/main.c: same + * s2s/main.c: same + * sm/main.c: same + + * etc/Makefile.am: get jabberd.cfg binary names correct based on configure options + * etc/jabberd.cfg.dist.in: same + +2003-08-12 Robert Norris + + * bootstrap: work with libtool 1.5 + + * c2s/c2s.c: free rate limit state at shutdown + * c2s/main.c: free rate limit state at shutdown; check authzid for validity + + * sx/io.c: free buffers + + * c2s/authreg.c: comment update + * sm/main.c: same + * util/nad.c: same + + * c2s/c2s.c: stupid logic bug checking for bounced sm request + + * sm/mod_template_roster.c: construct delete filter properly + + * sm/storage.c: free filter pool if filter parse fails + + * sx/env.c: free plugins and plugin data when environment is freed + * sx/sasl.c: same + * sx/ssl.c: same + * sx/sx.h: same + +2003-08-11 Robert Norris + + * configure.in: static builds + * ac-helpers/openssl.m4: same + +2003-08-10 Robert Norris + + * c2s/authreg_ldap.c: search as priveliged user, specify uid attribute in config + * etc/c2s.xml.dist.in: same + + * sm/dispatch.c: bounce non-unicast routes, handle invalid jids + + * sm/mod_offline.c: bounce queued messages on user delete + + * sx/sx.c: free chains on stream free + + * util/jid.c: use assertoin for jid_reset() + + * resolver/dns.c: comment update + * sm/mod_disco.c: same + * sm/mod_disco_publish.c: same + * sm/mod_iq_vcard.c: same + * sx/callback.c: same + * util/nad.c: same + +2003-08-09 Robert Norris + + * c2s/authreg.c: nasty potential crasher + +2003-08-08 Robert Norris + + * configure.in: -g for compilers is more portable than -ggdb + + * tools/db-setup.mysql: more escaping issues + + * c2s/authreg_mysql.c: sql templates + * c2s/authreg_pgsql.c: same + + * sm/pres.c: unavailables get broadcast to people we trust, not people who trust us + +2003-08-07 Robert Norris + + * idn/*: updated to libidn 0.2.1 + * configure.in: updated for idn changes + * util/jid.c: same + +2003-08-06 Robert Norris + + * sm/sess.c: session memory handled by pools + + * sm/sess.c: all sessions are active now, so no need to check + * sm/dispatch.c: same + * sm/mod_announce.c: same + + * sm/mod_disco.c: active sessions are now listed under a seperate node + + * sm/mod_disco_publish.c: free user data + * sm/mod_privacy.c: same + * sm/mod_roster.c: same + + * sm/mod_vacation.c: updated for vacation protocol 0.2 + + * sm/sm.h: cleanup + + * sx/error.c: brought stream errors into line with xmpp-core 16 + * sx/sx.h: same + + * sx/sasl.c: brought sasl errors into line with xmpp-core 16 + + * util/stanza.c: brought stanza errors into line with xmpp-core 16 + * util/util.h: same + + * s2s/main.c: expire old pending dns queries + +2003-08-05 Robert Norris + + * sm/sess.c: call user_create correctly (closes #2436) + + * sm/pkt.c: pkt_delay: x:delay stamp function + * sm/sm.h: same + * sm/mod_announce.c: use pkt_delay + * sm/pres.c: same + * sm/mod_offline.c: use pkt_delay; respond to offline events; expire messages + + * configure.in: allow openldap 2.0 + * ac-helpers/mysql.m4: check for stupid redhat systems + + * util/datetime.c: calculate timezone correctly + + * mio/mio.c: extra semicolon shouldn't be there + + * expat/internal.h: silence osx warnings + +2003-08-04 Robert Norris + + * c2s/authreg.c: registration remove + * c2s/c2s.c: registration remove, catch failed sm requests + * c2s/c2s.h: c2s->result now gets used for the register result too + * c2s/main.c: same + * c2s/sm.c: trigger user creation/deletion in the sm + + * etc/sm.xml.dist.in: more user-create and user-delete modules + + * router/router.c: broadcast packets + + * sm/user.c: user_load (user_get) now returns NULL if user not exist + user_create & user_delete delete by jid, not user + * sm/dispatch.c: support user_* changes + * sm/mm.c: same + + * sm/sess.c: remove protocol code + * sm/main.c: trigger c2s session ends (sess_end doesn't do it anymore) + + * sm/mod_active.c: cause user_load to fail on inactive users + * sm/mod_announce.c: delete data on user delete + * sm/mod_disco_publish.c: same + * sm/mod_iq_last.c: same + * sm/mod_iq_private.c: same + * sm/mod_iq_vcard.c: same + * sm/mod_offline.c: same + * sm/mod_privacy.c: same + * sm/mod_roster.c: same + * sm/mod_vacation.c: same + + * sm/mod_template_roster.c: populate rosters from template at user create (closes FR#2388) + * etc/templates/Makefile.am: same + * etc/templates/roster.xml.dist.in: same + + * sm/pres.c: no U list needed (after clarification from xmppwg) + + * sm/mod_roster.c: fix group deletion properly this time + + * sm/sm.h: support all of this + + * sm/storage_mysql.c: free filter after use + * sm/storage_pgsql.c: same + + * 2.0.0-a6 released + +2003-07-30 Robert Norris + + * c2s/authreg.c: allow 128 chars for packet ids + + * sm/mod_roster.c: correctly handle roster group deletions (closes #2395) + + * scod/mech_digest_md5.c: free attributes correctly + + * sm/mm.c: user-create and user-delete chains + * etc/sm.xml.dist.in: same + * sm/sess.c: better seperation of session activity from trigger packet; + support proper auto-creation of users; + moved active code into mod_active + * sm/user.c: moved active code into mod_active; + added user creation/deletion functions + * sm/mod_active.c: handle user active status + * sm/sm.c: moved packet dispatcher into dispatch.c + * sm/dispatch.c: move packet dispatcher here, with changes to support + user-create/user-delete chains and sess.c changes + + * sm/mod_disco_publish.c: user-load now returns int + * sm/mod_privacy: same + * sm/mod_roster.c: same + * sm/mod_vacation.c: same + + * sm/pkt.c: free packet nad only if it exists + + * sm/sm.h: changes to support all of this + +2003-07-28 Robert Norris + + * etc/sm.xml.dist.in: offline and announce need to get the first presence + + * sx/server.c: a smarter way to generate stream ids + + * sx/sasl.c: simple valgrind warning fixer + +2003-07-27 Robert Norris + + * c2s/authreg_ldap.c: user doesn't exist if we're unable to check + +2003-07-25 Robert Norris + + * sx/ssl.c: fixed ssl mode + * sx/sx.c: same + + * sm/pres.c: more logging, small distribution thinko + + * router/user.c: small leak + + * c2s/main.c: save process id files + * resolver/resolver.c: same + * router/main.c: same + * s2s/main.c: same + * sm/main.c: same + * etc/c2s.xml.dist.in: same + * etc/resolver.xml.dist.in: same + * etc/router.xml.dist.in: same + * etc/s2s.xml.dist.in: same + * etc/sm.xml.dist.in: same + + * router/router.c: packet throttling + * router/router.h: same + +2003-07-24 Robert Norris + + * sm/pres.c: new xmpp-compliant presence tracker + * sm/mod_presence.c: same + * sm/pkt.c: same + * sm/sm.h: same + * etc/sm.xml.dist.in: same + + * sm/sm.c: store sm sessions in hashtable; more unique session ids + * sm/main.c: same + * sm/sess.c: same + * c2s/c2s.c: same + * c2s/c2s.h: same + + * util/datetime.c: now generates correct timestamps + + * util/hex.c: hex conversion utilities + + * util/sha1.c: refactored to be more like md5_* + * util/sha1.h: same + * util/str.c: moved shahash_r here and refactored to use new hex utils + * util/sha.c: removed + + * util/util.h: updated to incorporate these changes + * util/Makefile.am: same + + * sx/server.c: shahash_r can't generate random strings anymore, so do it ourselves + + * scod/mech_digest_md5.c: use new hex utils + + * tools/db-setup.mysql: fixed quotes (closes #2382) + + * etc/router.xml.dist.in: fixed well-formedness bug (closes #2381) + +2003-07-22 Robert Norris + + * c2s/c2s.c: protect against packets in no namespace (closes #2322) + * resolver/resolver.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * sm/sm.c: same + * sx/io.c: same + * sx/sasl.c: same + * sx/ssl.c: same + + * s2s/out.c: correctly flag pending dns resolutions + + * s2s/out.c: leak and double-free fixes + * sm/mod_roster.c: same + * sx/sasl.c: same + + * configure.in: check for res_query + + * tools/Makefile.am: removed references to pass-sync.sh + +2003-07-21 Robert Norris + + * scod/mech_digest_md5.c: check return value of password methods, method flags + * scod/mech_plain.c: method flags + * scod/scod.c: same + * scod/scod.h: same + + * sx/sasl.c: new flags for password method availability + * sx/sasl.h: same + * resolver/resolver.c: same + * s2s/main.c: same + * sm/main.c: same + * c2s/main.c: same + + * router/user.c: user/pass tables + * router/main.c: same + * router/router.h: same + * router/Makefile.am: user/pass table for router users + * etc/router-users.xml.dist.in: same + * etc/router.xml.dist.in: same + * etc/Makefile.am: same + + * c2s/authreg.c: don't need password sync anymore + * c2s/main.c: same + * etc/c2s.xml.dist.in: same + +2003-07-17 Robert Norris + + * scod/mech_digest_md5.c: only send one server realm; report success + correctly; use callback for password and + authzid checking + * scod/mech_plain.c: use callback for password and authzid checking + * scod/scod.c: specify realm to server_start(), bugfixes + * scod/scod.h: more callback stuff + + * c2s/c2s.c: updated sasl code to use scod; router port is now 5347 + * c2s/c2s.h: same + * c2s/main.c: same + * resolver/resolver.c: same + * router/main.c: same + * s2s/main.c: same + * s2s/router.c: same + * sm/main.c: same + * sm/sm.c: same + * sx/sasl.c: same + * sx/sasl.h: same + + * README: removed references to cyrus-sasl + * configure.in: don't search for/link cyrus-sasl anymore + * ac-helpers/cyrus-sasl.m4: removed + + * etc/c2s.xml.dist.in: removed sasl config, router port is now 5347 + * etc/resolver.xml.dist.in: same + * etc/s2s.xml.dist.in: same + * etc/sm.xml.dist.in: same + + * c2s/Makefile.am: link with scod + * resolver/Makefile.am: same + * router/Makefile.am: same + * s2s/Makefile.am: same + * sm/Makefile.am: same + +2003-07-16 Robert Norris + + * scod/mech_digest_md5.c: working DIGEST-MD5 implementation (client & server) + * scod/scod.h: same + +2003-07-15 Robert Norris + + * scod/*: minimal sasl implementation, work in progress + * configure.in: same + * Makefile.am: same + +2003-07-10 Robert Norris + + * util/str.c: add j_strnchr, like strchr but only counts n chars + * util/util.h: same + + * util/md5.c: md5 hashing code + * util/md5.h: same + * util/util.h: same + * util/Makefile.am: same + + * c2s/c2s.c: small bugfix (deref jid correctly) + +2003-07-09 Robert Norris + + * s2s/main.c: fixed compile errors + * s2s/out.c: same + + * router/main.c: changed default port ot 5347 + * etc/router.xml.dist.in: same + +2003-07-08 Robert Norris + + * util/xdata.c: xdata parsing + * util/xdata.h: same + * util/util.h: same + +2003-07-07 Robert Norris + + * util/datetime.c: seems to work, hurrah + + * sm/mod_vacation: vacation messages + * sm/main.c: same + * sm/mm.c: same + * sm/sm.h: same + * tools/db-setup.pgsql: same + * tools/db-setup.mysql: same + * etc/sm.xml.dist.in: same + + * sm/mod_privacy.c: initialise vars properly + + * sm/mod_disco.c: browse is officially deprecated + * etc/sm.xml.dist.in: same + + * s2s/in.c: keepalives, queue expiry, invalid expiry + * s2s/main.c: same + * s2s/out.c: same + * s2s/s2s.h: same + + * sm/sm.h: use defines instead of enum for namespace numbers + * sm/pkt.c: same + +2003-07-06 Robert Norris + + * util/datetime.c: start of iso8601 datetime parser + * util/util.h: same + * util/Makefile.am: same + +2003-07-04 Robert Norris + + * sm/mod_privacy.c: cleanups and fixes, item/default storage + * sm/db-setup.mysql: privacy fixes, motd-messages table rename, comments + * sm/db-setup.pgsql: same + * sm/mod_announce.c: motd-messages table rename + +2003-07-01 Robert Norris + + * configure.in: get uint8_t in a portable way + * ac-helpers/ac_create_stdint_h.m4: same + * ac-helpers/ac_compile_check_sizeof.m4: same + * ac-helpers/Makefile.am: same + * mio/mio.h: same + * sx/sx.h: same + * util/util.h: same + + * sm/mod_roster.c: make string large enough for full jids + * util/stanza.c: same + + * configure.in: fix --enable-select + + * sm/mod_privacy.c: xmpp iq:privacy support (incomplete) + * sm/mm.c: same + * etc/sm.xml.dist.in: same + * sm/Makefile.am: build for mod_privacy + * tools/db-setup.pgsql: privacy list storage + * tools/db-setup.mysql: same + + * configure.in: don't clobber CFLAGS with --enable-developer (closes #2247) + + * sm/storage_mysql.c: small thinko (thanks shuo) (closes #2240) + + * s2s/in.c: refactored much of the internals, much simpler now + * s2s/main.c: same + * s2s/out.c: same + * s2s/router.c: same + * s2s/s2s.h: same + + * idn/toutf8: use ICONV_CONST to make call to iconv() portable without warnings (closes #2222) + +2003-06-25 Robert Norris + + * util/jid.c: stringprep cache, for speed (thanks temas!) + * util/util.h: same + * c2s/authreg.c: same + * c2s/c2s.c: same + * c2s/c2s.h: same + * c2s/main.c: same + * router/main.c: same + * router/router.c: same + * router/router.h: same + * s2s/in.c: same + * s2s/main.c: same + * s2s/out.c: same + * s2s/s2s.h: same + * sm/aci.c: same + * sm/main.c: same + * sm/mod_disco.c: same + * sm/mod_disco_publish.c: same + * sm/mod_roster.c: same + * sm/pkt.c: same + * sm/sess.c: same + * sm/sm.h: same + +2003-06-23 Robert Norris + + * configure.in: extra header check that idn asks for ; force libtool to link during configure (closes #2192) + + * c2s/authreg.c: more memory leaks + * c2s/c2s.h: same + * c2s/main.c: same + * resolver/resolver.c: same + * router/main.c: same + * sm/mm.c: same + * sm/mod_announce: same + * sm/sm.c: same + * sm/storage_mysql.c: same + * sm/storage_pgsql.c: same + +2003-06-20 Robert Norris + + * Makefile.am: don't distribute ac-helpers/CVS + * ac-helpers/Makefile.am: same + * configure.in: same + + * c2s/main.c: stupid crasher when getting realm with no to address (closes #2210) + + * router/main.c: numerous memory leaks + * router/router.c: same + * sm/mod_disco.c: same + * sm/sm.h: same + * sx/io.c: same + * sx/sasl.c: same + * sx/server.c: same + * sx/sx.c: same + * util/config.c: same + +2003-06-19 Robert Norris + + * sx/client.c: allow partial writes; better buffer management (closes #1905) + * sx/error.c: same + * sx/io.c: same + * sx/sasl.c: same + * sx/server.c: same + * sx/ssl.c: same + * sx/sx.c: same + * sx/sx.h: same + * s2s/sx.c: same + + * c2s/c2s.c: changed to use partial writes (closes #1905) + * resolver/resolver.c: same + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * sm/sm.c: same + + * util/nad.c: fix compiler warnings on some systems (closes #1905) + * util/util.h: same + + * c2s/main.c: more realm sanity checks + + * c2s/authreg.c: xmpp-style session start + * c2s/c2s.c: same + * c2s/c2s.h: same + * c2s/main.c: same + + * sx/sasl.c: xmpp-style sasl errors + + * sm/mod_disco_publish.c: disco item publishing + * sm/mod_disco.c: same + * sm/mm.c: same + * sm/Makefile.am: same + * etc/sm.xml.dist.in: same + * tools/db-setup.pgsql: same + * tools/db-setup.mysql: same + + * 2.0.0-a5 released + +2003-06-18 Robert Norris + + * sm/object.c: preserve object order (closes #2103) + * sm/storage_mysql.c: force object return order (closes #2103), correct storage of field names + * sm/storage_pgsql.c: force object return order (closes #2103) + * tools/db-setup.mysql: same + * tools/db-setup.pgsql: same + +2003-06-17 Robert Norris + + * c2s/authreg_pam.c: pam authentication module (closes FR#1209) + * c2s/authreg.c: same + * c2s/Makefile.am: build for pam module + * ac-helpers/pam.m4: checks for pam + * configure.in: checks for pam, set alt cflags first + +2003-06-12 Robert Norris + + * ac-helpers/openldap.m4: swap link order on lber & ldap, helps systems that don't have deep deps on libs + * ac-helpers/mysql.m4: only try to link, since running doesn't do anything + * ac-helpers/pgsql.m4: same + +2003-06-11 Robert Norris + + * configure.in: use libtool to build everything (closes #1937) + * c2s/Makefile.am: same + * expat/Makefile.am: same + * idn/Makefile.am: same + * mio/Makefile.am: same + * resolver/Makefile.am: same + * router/Makefile.am: same + * s2s/Makefile.am: same + * sm/Makefile.am: same + * sx/Makefile.am: same + * util/Makefile.am: same + +2003-06-10 Robert Norris + + * config.rpath: taken from gettext, needed for AM_ICONV to work on all platforms + + * ac-helpers/*: added -R to try and link runpaths correctly (possibly closes #1937) + + * sx/sx.h: removed fd requirement so sx is not completely io-agnostic (closes #1896) + * authreg/authreg.c: same + * router/router.c: same + + * tools/pass-sync.sh: example password sync script + +2003-06-09 Robert Norris + + * configure.in: require openssl 0.9.6b+ to better support Redhat + + * c2s/authreg.c: external password synchronisation, sane default realm + * c2s/c2s.h: same + * c2s/main.c: same + * etc/c2s.xml.dist.in: same + * etc/jabberd.cfg.dist.in: run s2s before c2s + * sx/sasl.c: allow authzid to differ from user@realm, minor cleanups + * sx/sasl.h: minor cleanups + + * sx/client.c: removed fd requirement so sx is not completely io-agnostic (closes #1896) + * sx/io.c: same + * sx/sasl.c: same + * sx/server.c: same + * sx/ssl.c: same + * sx/sx.c: same + * sx/sx.h: same + + * configure.in: include sys/types.h in various ipv6 checks (closes #1931) + +2003-06-05 Robert Norris + + * README: documentation updates + * etc/c2s.xml.dist.in: same + * etc/sm.xml.dist.in: same + * tools/db-setup.mysql: same + * tools/db-setup.pgsql: same + * configure.in: documentation updates and build cleanups + * c2s/authreg.c: change functions to ar_* and _ar_*, for consistency + * c2s/authreg_anon.c: same + * c2s/authreg_db.c: same + * c2s/authreg_pipe.c: same + * sm/storage_mysql.c: optionally disable transactions + * sm/storage_pgsql.c: same + * tools/db-setup.pgsql: initial setups for mysql storage + +2003-06-04 Robert Norris + + * sm/storage_mysql.c: mysql storage module + * sm/storage.c: same + * c2s/authreg_mysql.c: mysql authreg module + * c2s/authreg.c: same + * ac-helpers/mysql.m4: build for mysql + * configure.in: same + * sm/Makefile.am: same + * etc/sm.xml.dist.in: config for mysql + * etc/c2s.xml.dist.in: same + +2003-06-02 Robert Norris + + * sm/storage_pgsql.c: insert multiple objects correctly, and fixed some leaks + +2003-05-30 Robert Norris + + * configure.in: more task-based build system + * ac-helpers/*: same + * c2s/authreg.c: same + * c2s/authreg_anon.c: same + * c2s/authreg_db.c: same + * c2s/authreg_ldap.c: same + * c2s/authreg_pgsql.c: same + * c2s/authreg_pipe.c: same + * sm/storage.c: same + * sm/storage_db.c: same + * sm/storage_fs.c: same + * sm/storage_pgsql.c: same + +2003-05-29 Robert Norris + + * c2s/authreg_pgsql.c: postgresql authreg module + * c2s/Makefile.am: same + * c2s/authreg.c: same + * etc/c2s.xml.dist.in: same + * tools/db-setup.pgsql: updated schema for authreg + +2003-05-28 Robert Norris + + * configure.in: build for postgresql + * ac-helpers/pgsql.m4: same + * sm/Makefile.am: same + * sm/storage.c: postgresql storage module + * sm/storage_db.c: correct init return on failure + * sm/storage_fs.c: same + * sm/storage_pgsql.c: numerous fixes from testing, works now + +2003-05-27 Robert Norris + + * c2s/c2s.c: record activity at accept so we don't send a keepalive before the stream is established + * sm/storage_pgsql.c: postgresql storage module + * etc/sm.xml.dist.in: same + * tools/db-setup.pgsql: initial setups for postgresql storage + * sm/mod_announce.c: split storage into two seperate types to make the schema sane + +2003-05-21 Robert Norris + + * c2s/authreg_pipe.c: base64 pipe-auth passwords + * tools/pipe-auth.pl: same + * c2s/c2s.c: c2s keepalives + * c2s/c2s.h: same + * c2s/main.c: same + * etc/c2s.xml.dist.in: same + * sm/sess.c: correctly replace sessions (closes #1917) + * sm/sm.c: same + * sm/sm.h: same + +2003-05-20 Robert Norris + + * sm/mm.c: user load chain + * sm/mod_roster.c: same + * sm/user.c: same + * sm/sm.h: same + * etc/sm.xml.dist.in: same + * sm/mod_iq_last.c: don't reply to untrusted requested + * sm/pres.c: _jid_trust() is now pres_trust() + +2003-05-16 Robert Norris + + * sx/callback.c: handle empty namespace declarations + +2003-05-15 Robert Norris + + * util/stanza.c: fixed error code assertion + * util/util.h: error codes are >100 to prevent clashes with sm module return codes + * sm/mod_announce.c: xmpp stanza errors + * sm/mod_deliver.c: same + * sm/mod_disco.c: same + * sm/mod_iq_last.c: same + * sm/mod_iq_private.c: same + * sm/mod_iq_vcard.c: same + * sm/mod_offline.c: same + * sm/mod_roster.c: same + * sm/mod_validate.c: same + * sm/pkt.c: same + * sm/sm.c: same + * sm/sm.h: same + +2003-05-14 Robert Norris + + * resolver/resolver.c: xmpp stanza errors + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * sm/sess.c: same + * sm/sm.c: same + * util/nad.c: removed nad_error() and nad_tofrom() + * util/util.h: same + + * c2s/c2s.c: xmpp stream errors + * c2s/main.c: same + * router/router.c: same + * sx/client.c: same + * sx/io.c: same + * sx/server.c: same + * sx/ssl.c: same + * sx/sx.h: same + +2003-05-09 Robert Norris + + * util/stanza.c: xmpp stanza errors + * util/util.h: xmpp stanza errors, defines for namespace URIs + * c2s/authreg.c: use xmpp stanza errors, defines for namespace URIs + * c2s/c2s.c: defines for namespace URIs + * c2s/sm.c: same + * resolver/resolver.c: same + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * s2s/sx.c: same + * sm/main.c: same + * sm/mod_announce.c: same + * sm/mod_disco.c: same + * sm/mod_iq_last.c: same + * sm/mod_iq_private.c: same + * sm/mod_iq_time.c: same + * sm/mod_iq_version.c: same + * sm/mod_offline.c: same + * sm/mod_privacy.c: same + * sm/mod_roster.c: same + * sm/pkt.c: same + * sm/pres.c: same + * sm/sess.c: same + * sm/sm.c: same + * sm/sm.h: same + * sx/io.c: same + * sx/sasl.c: same + * sx/server.c: same + * sx/ssl.c: same + * sx/client.c: defines for namespace URIs, sx_client_init() triggers write correctly + +2003-05-09 Matthias Wimmer + + * etc/sm.xml.dist.in: corrected mismatched tag + * configure.in: added _mio_addrlen + * mio/mio.c: same + * mio/mio_inaddr.c: same + * mio/mio_inaddr.h: same + +2003-04-16 Robert Norris + + * util/queue.c: no reason to restrict null data + +2003-04-15 Robert Norris + + * sx/ssl.c: handle large buffers correctly + +2003-04-10 Robert Norris + + * sm/mod_disco.c: correct local identity handling (including config options) + * etc/sm.xml.dist.in: same + +2003-04-04 Robert Norris + + * sx/io.c: correct error/close handling + * c2s/c2s.c: don't need to force close after error any more + * resolver/resolver.c: same + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * sm/sm.c: same + * sm/mm.c: fix nasty crasher when module can't support requested chain + + * c2s/c2s.c: idle connection checks + * c2s/c2s.h: same + * c2s/main.c: same + * etc/c2s.xml.dist.in: same + +2003-04-03 Robert Norris + + * sm/storage_db.c: refactored for new storage layer + * sm/mod_roster.c: silly memory allocation bug + +2003-04-02 Robert Norris + + * sm/mod_announce.c: refactored for new storage layer + * sm/mod_iq_vcard.c: same + * sm/storage_fs.c: warns about its unstable nature + +2003-04-01 Robert Norris + + * etc/sm.xml.dist.in: no more private index, so nothing to do at session start + * sm/mod_iq_last.c: refactored for new storage layer + * sm/mod_iq_private.c: same + * sm/mod_offline.c: free object sets when we're done with them + * sm/user.c: same + * sm/mod_roster.c: group storage + * sm/object.c: track the set tail + + * sx/sx.h: new plugin method for freeing connection state on close + * sx/sx.c: same + * sx/sasl.c: free connection state on close + * sx/ssl.c: same + + * ac-helpers/openldap.m4: stupid missing comment [jaxp] + + * sx/ssl.c: catch transport closure correctly (closes #1411) + * c2s/c2s.c: close streams after errors (closes #1411) + * resolver/resolver.c: same + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * sm/sm.c: same + +2003-03-31 Robert Norris + + * sm/sm.h: new object-based storage layer + * sm/storage.c: same + * sm/object.c: data objects for storage + * sm/storage_fs: nasty filesystem backend for storage layer testing + * sm/mod_offline.c: refactored for new storage layer + * sm/mod_roster.c: same + * sm/sess.c: same + * sm/user.c: same + * util/nad.c: parse raw xml into a nad + * util/util.h: same + +2003-03-27 Robert Norris + + * router/router.c: make sure components get reverse notifications about legacy components + * sm/mod_disco.c: refactored and heavily optimised + + * util/base64.c: base64 encode/decode functions + * util/util.h: same + * util/str.c: removed old str_b64decode() function + + * router/router.c: use statically allocated jid buffers, so we don't need 2-4 large malloc's every packet + * util/jid.c: jid_reset() shouldn't free memory that it doesn't know the origin of + + * sm/mod_validate.c: allow subscription packets + +2003-03-25 Robert Norris + + * c2s/authreg_db.c: xhashes can't store empty strings, so hold the empty realm handle seperately (possibly closes #1411) + * util/xhash.c: iteration functions + * util/util.h: same + +2003-03-24 Robert Norris + + * configure.in: check for openssl >= 0.9.6d (admin should check for security holes) + * ac-helpers/openssl.m4: same + * c2s/main.c: avoid segfault on unknown realm + +2003-03-21 Robert Norris + + * 2.0.0-a4 released + +2003-03-20 Robert Norris + + * router/main.c: packet logging facilities (log sinks) + * router/router.c: same + * router/router.h: same + * etc/router.xml.dist.in: same + +2003-03-19 Robert Norris + + * ac-helpers/*: alternate paths for external libs + * configure.in: same + + * sx/sasl.c: callback to get sasl auth realm + * sx/sasl.h: same + + * c2s/authreg.c: sasl client auth + * c2s/c2s.c: same + * c2s/c2s.h: same + * c2s/main.c: same + * etc/c2s.xml.dist.in: same + + * router/router.c: move component auth into seperate realm + * router/main.c: same + + * resolver/resolver.c: modified for sx sasl change + * s2s/main.c: same + * sm/main.c: same + + * configure.in: ldap support + * ac-helpers/openldap.m4: same + + * c2s/authreg_ldap.c: ldap authentication module + * c2s/authreg.c: same + * etc/c2s.xml.dist.in: same + + * c2s/c2s.h: pull in config.h + * idn/strinprep.h: same + * mio/mio.h: same + * resolver/resolver.h: same + * router/router.h: same + * s2s/s2s.h: same + * sm/sm.h: same + * sx/sx.h: same + + * expat/*: updated to 1.95.6 + +2003-03-18 Robert Norris + + * sx/sasl.c: force servers to explicitly offer sasl/starttls + * sx/sasl.h: same + * sx/ssl.c: same + * sx/ssl.h: same + + * c2s/c2s.c: authenticate to router with seperate user/pass; seperate connect and accept SSL pemfiles + * c2s/main.c: same + * c2s/c2s.h: same + * resolver/resolver.c: same + * resolver/resolver.h: same + * s2s/in.c: same + * s2s/main.c: same + * s2s/out.c: same + * s2s/router.c: same + * s2s/s2s.h: same + * sm/main.c: same + * sm/sm.c: same + * sm/sm.h: same + * etc/c2s.xml.dist.in: same + * etc/resolver.xml.dist.in: same + * etc/router.xml.dist.in: same + * etc/s2s.xml.dist.in: same + * etc/sm.xml.dist.in: same + + * sm/main.c: version signature + * sm/mod_iq_version.c: same + * sm/sm.h: same + + * router/main.c: name aliases; bind access controls + * router/router.c: same + * router/router.h: same + + * sm/mod_iq_vcard.c: sanity checks for dodgy data + * sm/mod_iq_private.c: same + * sm/mod_announce.c: same + * sm/mod_offline.c: same + + * s2s/out.c: cache dns results + * s2s/s2s.h: same + +2003-03-14 Robert Norris + + * tools/jabberd.in: run s2s by default + * etc/jabberd.cfg.dist.in: same + + * c2s/c2s.c: write large buffers out + * resolver/resolver.c: same + * router/router.c: same + * s2s/in.c: same + * s2s/out.c: same + * s2s/router.c: same + * sm/sm.c: same + * sx/sasl.c: correctly encode large buffers + +2003-03-13 Robert Norris + + * s2s/*: s2s (dialback) component + * etc/s2s.xml.dist.in: config for s2s + * configure.in: build system for s2s + * router/router.c: support for a "default" bind option + * router/router.h: same + * c2s/c2s.c: weird indenting fixed + * sm/sm.h: no conflicting bitmasks + * sx/ssl.h: bit-shifted flag the wrong way + +2003-03-11 Robert Norris + + * configure.in: set berkeley include and lib paths properly + * berkeley-db.m4: same + + * router/router.c: route advertisements + * router/router.h: domain_t doesn't exist anymore + * sm/mod_disco.c: updated for new advertisement protocol + * sm/pkt.c: recognize and build advertisement packets + * sm/sm.h: same + * sm/sm.c: dispatch non-route packets to pkt_router chain + + * router/router.c: support for legacy 'jabber:component:accept' components + * router/main.c: same + * etc/router.xml.dist.in: same + * util/nad.c: errors don't have to have text, the code is enough + + * resolver/resolver.c: update for SX2 and new component protocol + * resolver/resolver.h: same + * etc/resolver.xml.dist.in: same + * c2s/main.c: free mio at exit + * sm/main.c: same + * router/main.c: same + * sx/ssl.c: fixed ssl/sasl establishment checks + * sx/sasl.c: same + * sx/sx.c: same + +2003-03-10 Robert Norris + + * sx/sasl.c: updated to xmpp-core-05 + * sx/sasl.h: same + * sx/ssl.c: same + * sx/ssl.h: same + * sx/client.c: supporting changes + * sx/io.c: same + * sx/server.c: same + * c2s/c2s.c: encrypt the router conn if possible + * sm/main.c: same + * sm/sm.c: same + * sm/sm.h: same + + * README: copyright and licensing info + * idn/*: import of GNU Libidn 0.1.11 + * configure.in: checks for iconv and locale (for idn) + * Makefile.am: build idn + * c2s/Makefile.am: link libidn.a + * sm/Makefile.am: same + * router/Makefile.am: same + * resolver/Makefile.am: same + * util/jid.c: prep new jids, use xmpp-core-05 field widths (closes #809) + * util/util.h: same + * c2s/authreg.c: prep modified session jid + * router/router.c: prep domain names on incoming packets + * sm/pres.c: verify trust using prep'd jids directly + + * berkeley-db.m4: alternate paths for berkeley db (actions #791) + * configure.in: same + +2003-03-05 Robert Norris + + * sx/Makefile.am: removed handshake.[ch] + * sx/callback.c: start nad depth at 0, track namespaces for attributes + * sx/client.c: track namespaces for attributes + * sx/server.c: same + * sx/io.c: better close handling + * sx/chain.c: starting element for nads + * sx/sasl.c: same + * sx/sx.c: track callback reentry correctly + * sx/sx.h: header changes for all this + + * configure.in: removed debugging flags + * c2s/authreg_pipe.c: redundant buffer removed + * c2s/c2s.c: ported to new SX2 and adjusted for new packet format + * c2s/c2s.h: same + * c2s/main.c: same + * c2s/sm.c: same + * sm/*: ported to new SX2 and adjusted for new packet format + * router/router.c: route errors, unbind names on disconnect + * sx/io.c: make sx_nad_write_elem() actually write from elem + * util/jid.c: TODO item about libidn + * util/nad.c: nad_append_namespace(), changes to nad_error() + * util/util.h: same + * etc/c2s.xml.dist.in: router is now on port 5230 + * etc/sm.xml.dist.in: same + * docs/dev/sm-c2s-protocol: new session protocol + * docs/dev/component-protocol: new component protocol + + * c2s/c2s.c: fixed router reconnect + * sm/sm.c: same + +2003-03-03 Robert Norris + + * router/main.c: new route protocol, early ssl/sasl component support + * router/main.c: same + * router/router.h: same + +2003-02-26 Robert Norris + + * sx/sx.c: don't allow sx_free to be called from a callback + * sx/io.c: same + * router/main.c: same + * router/router.c: same + +2003-02-25 Robert Norris + + * README: warning about broken CVS + * sx/*: new streams layer + * c2s/*: partial port to new SX2 + * router/*: port to new SX2 + * etc/router.xml.dist.in: same + * util/nad.c: nad_tofrom() and nad_error() utilities from SX1 + * util/util.h: same + * configure.in: get reasonable debug flags with --enable-debug + +2003-02-09 Matthias Wimmer + + * README: The URL to download Berkeley DB changed + +2003-01-16 Robert Norris + + * sm/mod_roster.c: fixed a little bug that could break group management + * router/main.c: service aliases + * router/router.h: same + * etc/router.xml.dist.in: same; 1.4 uplink example + * router/router.c: allow ip/port fallback to work correctly + * sx/conn.c: fill out conn peer structure correctly + +2003-01-10 Robert Norris + + * sm/mod_roster.c: s10n for jids not on the roster get created correctly (closes #864) + +2003-01-08 Matthias Wimmer + + * mio/mio_inaddr.h: fixed a typo + +2003-01-07 Matthias Wimmer + + * configure.in: check for in_port_t added + +2002-12-20 Robert Norris + + * sm/mm.c: removed per-module module data (redundant) + * sm/sm.h: same + * sm/mod_announce.c: same + * sm/mm.c: module instance data passed to each module + * sm/mod_*.c: same + * sm/sm.h: same + + * 2.0.0-a3 released + +2002-12-18 Robert Norris + + * configure.in: IPv6 support [mawis] + * c2s/c2s.c: same + * c2s/main.c: same + * mio/mio.c: same + * mio/mio.h: same + * mio/mio_inaddr.c: same + * mio/mio_inaddr.h: same + * resolver/dns.c: same + * resolver/dns.h: same + * resolver/resolver.c: same + * resolver/resolver.h: same + * router/main.c: same + * router/router.c: same + * sx/conn.c: same + * sx/dialback.c: same + * sx/sasl.c: same + * sx/server.c: same + * sx/sx.h: same + * util/access.c: same + * util/util.h: same + * util/inaddr.c: same + * util/util_compat.h: same + * etc/resolver.xml.dist.in: same + +2002-12-17 Robert Norris + + * sm/mod_announce.c: broadcast messages (broadcast, motd, etc) + * sm/mm.c: same + * etc/sm.xml.dist.in: same + * sm/mm.c: global per-module data + * sm/sm.h: same + * util/nad.c: rewrote nad_find_scoped_namespace() + * sm/mod_echo.c: send sm message to admins + * sm/mm.c: same + * etc/sm.xml.dist.in: same + +2002-12-16 Robert Norris + + * sm/mod_deliver.c: packets to offline resources return 404 + * sm/mm.c: per-instance module arguments (for module multiplexors eg mod_perl) + * sm/mod_*.c: same + * sm/sm.h: same + +2002-12-14 Robert Norris + + * util/jid.c: jid list helpers moved from sm/pres.c + * util/util.h: same + * sm/pres.c: same + * sm/aci.c: access control specification and checking + * sm/main.c: same + * sm/sm.h: same + * etc/sm.xml.dist.in: same + * sm/disco.c: user/session disco now restricted + +2002-12-09 Robert Norris + + * sm/mod_disco.c: set feature element at the correct depth (closes #792) + * util/nad.c: added a note about a needed fix to use of parents and the depth array + +2002-12-04 Robert Norris + + * c2s/authreg_pipe.c: pipe authenticator + * c2s/authreg.c: same + * etc/c2s.xml.dist.in: same + * tools/pipe-auth.pl: sample module for pipe authenticator + * c2s/c2s.c: flag chunks from domains (no node) correctly + * sm/mod_deliver.c: send everything not for this session instead of this user + * sm/mod_disco.c: list of active users, and active sessions per user + +2002-12-01 Robert Norris + + * router/router.c: component presence + * c2s/c2s.c: same + * resolver/resolver.c: same + * sm/sm.c: same + * sm/mod_disco.c: service discovery (JEP-0030) support, agents/browse compatibiltiy + * sm/main.c: same + * sm/mm.c: same + * sm/sm.h: same + * etc/sm.xml.dist.in: same + * sm/feature.c: feature registry + * sm/: updated modules to register supported features + * sm/storage.c: removed redundant code + * sm/mm.c: prevent infinite loops + * sm/pkt.c: tiny packet id bugfix + +2002-11-28 Robert Norris + + * c2s/main.c: SIGHUP now rotates the log + * resolver/resolver.c: same + * router/main.c: same + * sm/main.c: same + * c2s/main.c: reorganised some code slightly + * sm/main.c: same + +2002-11-26 Robert Norris + + * sm/mm.c: setup module_t fields before the initialiser gets called + +2002-11-22 Robert Norris + + * sm/storage_db.c: restructured to make more sense (and work correctly) + * sm/mod_iq_private.c: rewritten to make more effective use of utils + * sm/mm.c: same + * sm/sm.c: log connection info like c2s/resolver + * sm/mod_iq_vcard.c: return 501 if put isn't implemented, not 500 + + * 2.0.0-a2 released + + * sm/mod_iq_private.c: no more namespace restrictions + +2002-11-21 Robert Norris + + * c2s/authreg_db.c: tranaction/recovery support + * sm/storage_db.c: same + * util/xhash.c: added xhash_zapx() and xhash_putx() + * util/util.h: same + +2002-11-20 Robert Norris + + * sx/chunk.c: now has the concept of local/remote for peers, and experimental support for the new router stream + * sx/client.c: same + * sx/conn.c: same + * sx/dialback.c: same + * sx/nad.c: same + * sx/sasl.c: same + * sx/server.c: same + * sx/sx.h: same + * c2s/: updated to support changed api + * resolver/: same + * router/: same + * sm/: same + * configure.in: only support db 4.1 + * sm/storage_db.c: same + * c2s/authreg_db.c: same + +2002-11-19 Robert Norris + + * c2s/sm.c: new c2s/sm control protocol (see docs/dev/sm-c2s-protocol) + * c2s/c2s.c: same + * c2s/authreg.c: same + * c2s/c2s.h: same + * sm/main.c: same + * sm/pkt.c: same + * sm/sess.c: same + * sm/sm.c: same + * sm/sm.h: same + * util/jid.c: clear dirty flag when we expand + +2002-11-18 Robert Norris + + * etc/: more changes to config building, now we insert correct paths into xml files + * util/nad.c: nad_wrap_elem() brings new namespaces into scope + * util/util.h: same + * c2s/c2s.c: converted to run on jid_* + * c2s/authreg.c: same + * c2s/c2s.h: same + +2002-11-15 Robert Norris + + * util/nad.c: nad_set_attr now takes a length for the value, so no restrictions on packet id length + * util/util.h: same + * c2s/authreg.c: same + * c2s/c2s.c: same + * resolver/resolver.c: same + * sm/mod_deliver.c: same + * sm/mod_iq_last.c: same + * sm/mod_iq_private.c: same + * sm/mod_iq_time.c: same + * sm/mod_iq_vcard.c: same + * sm/mod_iq_version.c: same + * sm/mod_offline.c: same + * sm/mod_privacy.c: same + * sm/mod_roster.c: same + * sm/pkt.c: same + * sm/pres.c: same + * sm/sess.c: same + * sm/sm.h: same + * sx/dialback.c: same + * sx/nad.c: same + * sx/sasl.c: same + * configure.in: install config under $(sysconfdir)/jabber + +2002-11-14 Ryan Eatmon + + * Makefile.am: include tools + * configure.in: same + * etc/jabberd.cfg.dist: config file for jabberd wrapper + * etc/Makefile.am: same + * tools/jabberd: jabberd wrapper script to provide easier launching + * tools/Makefile.am: same + * util/log.c: flush the debug output + +2002-11-14 Robert Norris + + * resolver/dns.h: explicitly include some headers (closes #728) + * configure.in: link with -lcrypto (closes #728) + * sm/pres.c: make sure _jid_trust build the user jid correctly (closes #733) + * util/serial.c: nice string/int serialiser functions + * util/util.h: same + * util/Makefile.am: same + * sm/mod_iq_private.c: fixed compile warnings + * sm/mod_roster.c: modified to use new serialiser utils + * sm/mod_privacy.c: same + +2002-11-13 Robert Norris + + * sm/mm.c: actually update mm->nindex (I have no idea how this worked before) + * sm/sm.h: per-user module data + * sm/user.c: same + * sm/sess.c: send through unavailable presence before we call mm_sess_end(); + * sm/sess.c: free module data on session end + * sm/mod_privacy.c: jabber:iq:privacy (JEP-0016) support + * sm/sm.h: same + * sm/main.c: same + * sm/mm.c: same + * sm/Makefile.am: same + * etc/sm.xml.dist: same + * sm/mod_iq_private.c: jabber:iq:privacy (JEP-0049) support + * sm/mm.c: same + * sm/Makefile.am: same + * etc/sm.xml.dist: same + * sm/mod_echo.c: message echo module (closes FR#375) + * sm/Makefile.am: same + * sm/mm.c: same + * sm/Makefile.am + * etc/sm.xml.dist: same + * sm/sess.c: new function sess_match(), finds a session by resource (full or partial) + * sm/sm.h: same + * sm/mod_deliver.c: modified to use sess_match() + * sm/mod_privacy.c: same + * sm/mod_roster.c: send presence from top session with automated (un)sub responses + +2002-11-12 Robert Norris + + * util/nad.c: started a todo list + +2002-11-11 Robert Norris + + * util/log.c: removed restriction on filename length (closes #714) + * util/util.h: same + +2002-11-08 Robert Norris + + * sm/mod_iq_vcard.c: overwrite existing vcard properly + * c2s/authreg_db.c: support db 4.1+ + * sm/storage_db.c: same + * etc/Makefile.am: don't overwrite existing config files if present (closes #705) + * etc/c2s.xml.dist: moved from etc/c2s.xml + * etc/router.xml.dist: moved from etc/router.xml + * etc/resolver.xml.dist: moved from etc/resolver.xml + * etc/sm.xml.dist: moved from etc/sm.xml + * sx/dialback.c: lowercase domain names + * sx/conn.c: handle empty namespace declarations + * util/jid.c: added a note about jid comparisions + +2002-11-07 Ryan Eatmon + + * util/log.c: fixed logging of notices when debug is turned on + * c2s/main.c: reconnect to router on router disconnect + * resolver/resolver.c: same + * sm/main.c: same + +2002-11-07 Robert Norris + + * 2.0.0-a1 released diff --git a/Doxyfile.in b/Doxyfile.in new file mode 100644 index 00000000..71f6933b --- /dev/null +++ b/Doxyfile.in @@ -0,0 +1,1101 @@ +# Doxyfile 1.3.3 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = jabberd2 + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @VERSION@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs/code + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, +# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en +# (Japanese with English messages), Korean, Norwegian, Polish, Portuguese, +# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = . + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explict @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 10 + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = NO + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = mio scod sx util c2s resolver router sm s2s + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl *.odl *.cs + +FILE_PATTERNS = *.c *.h *.dox + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories +# that are symbolic links (a Unix filesystem feature) are excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = . + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output dir. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse the +# parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superceded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similiar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes that +# lay further from the root node will be omitted. Note that setting this option to +# 1 or 2 may greatly reduce the computation time needed for large code bases. Also +# note that a graph may be further truncated if the graph's image dimensions are +# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). +# If 0 is used for the depth value (the default), the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = search.cgi + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = /usr/local/bin/ + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..96ed1a20 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,6 @@ +EXTRA_DIST = PROTOCOL Doxyfile.in README.win32 contrib + +SUBDIRS = etc tools man expat mio subst sx util c2s resolver router s2s sm + +docs: Doxyfile + @doxygen diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..c6cdaaea --- /dev/null +++ b/NEWS @@ -0,0 +1,144 @@ +2005-03-31 jabberd 2.0s7 + * I have been using ChangeLog to record this info - justin + * See the wiki at http://j2.openaether.org/mediawiki for real news + +2004-06-25 jabberd 2.0s3 + + * Fixed several memory leaks and overruns + * Fixed XMPP session replacement [rob] + * Added support for Win32 platforms [rob] + * Added support for requiring SSL/TLS before auth + * Added support for LDAPv3 (including channel encryption) [rob] + * Added workaround to make large presence broadcasts more efficient [rob] + * Generate random dialback key if none provided [rob] + * Rewrote configure script (+ many code tweaks to support this) [rob] + * Remove build requirement for Libidn and OpenSSL [rob] + * Removed bundled Libidn due to licensing issues [rob] + * Bugfixes: 3059, 3174, 3343, 3368, 3480, 3481, 3594 + +2004-02-10 jabberd 2.0s2 + + * Brought privacy lists into line with latest spec + * Fixed registration bug & password changing security hole [rob] + * Fixed DIGEST-MD5 "qop" attribute handling [rob] + * Fixed a number of c2s/s2s I/O race conditions [rob] + * Fixed keepalives (now work with SSL/TLS connections) [rob] + * Fixed build against OpenLDAP 2.2 [rob] + * Updated presence broadcast semantics [rob] + * Added calls to PAM account management functions + * Added handling for zero-length LDAP passwords [rob] + * Added checks for Berkeley DB 4.2 [rob] + * Added check for vsyslog() and workaround code for systems that don't have it (eg HP-UX) [rob] + * Removed -R flags from link directives [rob] + +2003-12-28 jabberd 2.0s1 + +2003-12-10 jabberd 2.0rc2 + +2003-11-17 jabberd 2.0rc1 + +2003-11-03 jabberd 2.0b3 + + * Support for multiple SRV lookups [rob] + * c2s/sm detect when the other is unavailable, and kills sessions appropriately [rob] + * Optional logging to stdout [rob] + +2003-10-08 jabberd 2.0b2 + + * 1.4 migration tool [rob] + * Configurable router reconnect [rob] + * Lots of memory corruption and other bugs fixed [rob] + +2003-09-03 jabberd 2.0b1 + + * Offline message event (JEP-0022) support [rob] + * Message expiration (JEP-0023) support [rob] + * Libidn updated to 0.2.1 [rob] + * SQL templates for MySQL/PostgreSQL authreg modules + * Static builds (experimental) [rob] + * Optionally allow in-band password changes instead of full account creation [rob] + * Allow multiple s2s instances [mawis] + * Optionally restrict available authentication mechanisms [rob] + * SASL ANONYMOUS support [rob] + +2003-08-04 jabberd 2.0.0-a6 + + * XMPP privacy list support [rob] + * Stringprep cache (= significant performance increase) [rob] + * Internal SASL implementation (no dependency on Cyrus-SASL) [rob] + * Removed c2s external password synchronisation (not needed without Cyrus-SASL) [rob] + * s2s keepalives [rob] + * s2s queue expiry [rob] + * s2s invalid expiry [rob] + * XMPP-compliant presence tracker [rob] + * PID files [rob] + * Component packet throttling [rob] + * Broadcast packets [rob] + * Roster templates [rob] + +2003-06-19 jabberd 2.0.0-a5 + + * Build system overhaul [rob] + * Query-based storage layer [rob] + * PostgreSQL storage and authentication [rob] + * MySQL storage and authentication [rob] + * PAM authentication [rob] + * Incremental roster database updates [rob] + * c2s idle connection checks [rob] + * c2s keepalives [rob] + * c2s external password synchronisation [rob] + * Disco identity configuration [rob] + * Disco item publish [rob] + * XMPP-style errors [rob] + * XMPP-style session start [rob] + +2003-03-21 jabberd 2.0.0-a4 + + * Rewritten streams library [rob] + * STARTTLS and SASL updated to xmpp-core-05 [rob] + * JID preparation (xmpp-core section 3) [rob] + * New component routing protocol [rob] + * External s2s component [rob] + * Components can encrypt router session [rob] + * Name aliases (supports 1.4 uplink components) [rob] + * Component access controls [rob] + * SM version signature [rob] + * configure can take alternate paths for external libs [rob] + * LDAP authentication module [rob] + * Expat updated to 1.95.6 [rob] + * Facilities for packet logging components [rob] + +2002-12-20 jabberd 2.0.0-a3 + + * Component presence & service discovery [rob] + * Pipe authenticator [rob] + * SIGHUP reopens logfiles [rob] + * Access control for priviliged operations [rob] + * Support for module multiplexors (eg mod_perl) [rob] + * Broadcast messages (announce, motd, etc) [rob] + * Resend messages for SM to admins [rob] + * IPv6 support [mawis] + +2002-11-22 jabberd 2.0.0-a2 + + * Reconnect code added to resolver, c2s, and sm. [reatmon] + * jabber:iq:privacy (JEP-0016) support [rob] + * jabber:iq:private (JEP-0049) support [rob] + * Message echo support [rob] + * jabberd wrapper script [reatmon] + * New c2s/sm command protocol [rob] + * Transaction/recovery support for Berkeley DB modules [rob] + +2002-11-07 jabberd 2.0.0-a1 + + * Core IM support in sm (messages, presence, rosters, s10ns) [rob] + * Config files for c2s/resolver/router [rob] + * c2s auth/reg subsystem [rob] + * IP-based access controls [rob] + * Connection rate limiting [rob] + * Karma (byterate limiting) [rob] + * vCard support [rob] + * Basic README [rob] + * Upgrade to Expat 1.95.5 [rob] + * Autotools-based build system [temas] + * Debug clean ups [reatmon] diff --git a/PROTOCOL b/PROTOCOL new file mode 100644 index 00000000..7d23f933 --- /dev/null +++ b/PROTOCOL @@ -0,0 +1,111 @@ +Protocol support +---------------- + +jabberd 2.0 is a server implementation of the eXtensible Messaging and +Presence Protocol (XMPP), as published by the IETF. It also implements +several XMPP extensions documented by the Jabber Software Foundation +(JSF), and some legacy extensions that were implemented by its +predecessor, jabberd 1.4. + +This document lists the protocols supported by the server, and any notes +relating to the implementation. + +This is current as of 2005-04-04 (jabberd 2.0s8). + + +XMPP +---- + +XMPP Core (RFC 3920) + + Implemented, except for: + - SASL for s2s streams + - Language support via xml:lang + +XMPP IM (RFC 3921) + + Implemented. + + +JSF Extensions +-------------- + +Jabber Browsing (JEP-0011) + + Implemented by the session manager (mod_disco) as a wrapper around the + service list used for Service Discovery. The configuration required to + enable browsing is undocumented as browse is considered to be + deprecated. + +Last Activity (JEP-0012) + + Implemented by the session manager (mod_iq_last). + +Message Events (JEP-0022) + + Offline event implemented by the session manager (mod_offline). + +Message Expiration (JEP-0023) + + Implemented by the session manager (mod_offline). + +Service Discovery (JEP-0030) + + Implemented by the session manager (mod_disco). Administrative users + will see extra nodes when doing a #items query - these nodes provide + information about active users and sessions. New components becoming + available are probed automatically, and if they are disco-aware, their + information is added to the service list. Sub-entity item publishing is + implemented seperately by mod_disco_publish. + +Private XML Storage (JEP-0049) + + Implemented by the session manager (mod_iq_private). + +vcard-temp (JEP-0054) + + Implemented by the session manager (mod_iq_vcard). + +In-band Registration (JEP-0077) + + Implemented by c2s. + +Non-SASL Authentication (JEP-0078) + + Implemented by c2s. + +Legacy Errors (JEP-0086) + + Implemented. + +Entity Time (JEP-0090) + + Implemented by the session manager (mod_iq_time). + +Delayed Delivery (JEP-0091) + + Implemented by the session manager. + +Software Version (JEP-0092) + + Implemented by the session manager (mod_iq_version). + +Agent Information (JEP-0094) + + Implemented by the session manager (mod_disco) as a wrapper around the + service list used for Service Discovery. + +External Component Protocol (JEP-0114) + + Implemented by the router as a wrapper around the more featureful + jabberd 2.0 component protocol. + + +Legacy extensions (jabberd 1.4) +------------------------------- + +Invisible presence + + Implemented by the session manager. Note that the JSF version of this + as documented in JEP-0018 is substantially different to the traditional + implementation. diff --git a/README b/README new file mode 100644 index 00000000..203efffe --- /dev/null +++ b/README @@ -0,0 +1,204 @@ +README for Jabber Open Source Server (2.0s8) + +Thanks for downloading jabberd 2.0. Below are some basic instructions to +get you started. Complete documentation is available at +http://jabberd.jabberstudio.org/2/docs/ + +-- the jabberd team + + +Optional packages: + + - GNU Libidn (0.3.0 or higher) - needed for JID canonicalisation + http://www.gnu.org/software/libidn/ + - OpenSSL (0.9.6b or higher) - needed for SSL/TLS support + http://www.openssl.org/news/ + - Berkeley DB (4.1.24 or higher) + http://www.sleepycat.com/download/ + - OpenLDAP (2.1.0 or higher) + http://www.openldap.org/software/download/ + - PostgresSQL (development libraries and headers) + http://www.postgresql.org/ + - MySQL (development libraries and headers) + http://www.mysql.com/ + - PAM + http://www.linux-pam.org/ (for Linux) + + +Build: + + % ./configure + % make + % make install + +Options to ./configure: + + --prefix=/path/to/install + Where to install everything + + --enable-idn (default: enabled) + Compile with IDN (Stringprep) support + + --enable-ssl (default: enabled) + Compile with SSL/TLS support + + --enable-mysql (default: enabled) + Compile MySQL auth/reg/storage support + + --enable-pgsql (default: disabled) + Compile PostgreSQL auth/reg/storage support + + --enable-db (default: disabled) + Compile Berkeley DB auth/reg/storage support + + --enable-ldap (default: disabled) + Compile OpenLDAP auth/reg support + + --enable-pam (default: disabled) + Compile PAM auth/reg support + + --enable-pipe (default: disabled) + Compile pipe auth/reg support + + --enable-anon (default: disabled) + Compile anonymous auth support + + --enable-fs (default: disabled) + Compile filesystem storage support (NOT RECOMMENDED) + + --enable-mio=BACKEND + Specify MIO backend to use (poll or select) + + --with-extra-include-path + --with-extra-library-path + Add extra header/library search paths + + --enable-debug (default: disabled) + Output debugging info when run with -D + + --enable-nad-debug (default: disabled) + Compile with NAD pointer tracking + + --enable-pool-debug (default: disabled) + Compile with pool statistics + + --enable-developer (default: disabled) + Compile with full warnings and debugging symbols + + +Configure: + + Edit $prefix/etc/(router|sm|c2s|resolver|s2s).xml to taste. In + particular, make sure you setup for your choice of data storage + correctly. If you're using the Berkeley DB backend, you'll need to + create /var/run/jabberd and sets its permissions so that the server + processes can find it. + + If you're using a SQL backend, you'll need to create an account for + the server to use, and create the tables. Load db-setup.mysql or + db-setup.pgsql from the tools/ directory into your database to do + this. + + If you plan to use the jabberd wrapper script, make sure you look at + the paths in the $prefix/etc/jabber/jabberd.cfg. + + +Run: + + You can either run all of the pieces separately: + + % $prefix/bin/router & + % $prefix/bin/resolver & + % $prefix/bin/s2s & + % $prefix/bin/sm & + % $prefix/bin/c2s & + + Or you can run them all from the jabberd wrapper script: + + % $prefix/jabberd & + +All the processes can take the following switches: + + -c use an alternate config file + -D output lots of debugging info (if compiled with --enable-debug) + + +Support: + +Documentation: http://jabberd.jabberstudio.org/2/docs/ +Wiki: http://j2.openaether.org/mediawiki + + Bugs to: http://j2.openaether.org/bugzilla/ +Requests to: http://j2.openaether.org/bugzilla/ + +Everything else: jabberd@jabberstudio.org + (Subscribe at http://mail.jabber.org/mailman/listinfo/jabberd + Archives at http://mail.jabber.org/pipermail/jabberd/ ) + +When requesting assistance, please note that the following things can +provide useful information which may assist with finding your problem: + + - debug logs (compile with --enable-debug and run with -D) + - running components seperately (ie without the wrapper script) + - config.log + +Please try to provide as much relevant information as possible when +reporting problems - it will make helping you much easier. + + +Copyright & License: + + jabberd - Jabber Open Source Server + Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + Ryan Eatmon, Robert Norris. + + 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + + As a special exception, the authors give permission to link this + program with the OpenSSL library and distribute the resulting binary. + + + Includes Expat (1.95.7). + Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd + and Clark Cooper. + Copyright (c) 2001, 2002 Expat maintainers. + + subst/snprintf.c and util/base64.c were originally taken from the + Apache web server project. + Originally copyright (c) 1995-2003 Apache Software Foundation. + + util/md5.c was taken from Ghostscript. + Originally copyright (c) 1999-2002 Aladdin Enterprises. + + util/sha1.c was taken from Mozilla. + Originally copyright (c) 1995-1999 Cryptography Research, Inc. + + subst/getopt.[ch] was taken from GNU Libc. + Originally copyright (c) 1987-1993 Free Software Foundation, Inc. + + subst/gettimeofday.c was taken from PostgreSQL. + Originally copyright (c) 2003 SRA, Inc. & SKC, Inc. + + subst/syslog.[ch] was taken from Bind. + Originally copyright (c) 2001 Internet Software Consortium. + + subst/inet_aton.c + Originally copyright (c) 1995-1997 Kungliga Teniska Hogskolan + + subst/ip6_misc.h + Originally copyright (c) 1993,1994,1997 The Regents of the University of California. + + subst/dirent.[ch] + Originally copyright (c) 1997,2003 Kevlin Henney. diff --git a/README.win32 b/README.win32 new file mode 100644 index 00000000..4d9eb87d --- /dev/null +++ b/README.win32 @@ -0,0 +1,40 @@ +jabberd2 for win32 +------------------ + +As of 2.0s7, there are patches and VisualStudio projedt files at http://j2.openaether.org/bugzilla/show_bug.cgi?id=36 thanks to Peter Hinz + +As of 2.0s3, jabberd2 includes HIGHLY EXPERIMENTAL support for win32 +platforms. + +Note that DNS resolution APIs did not appear until Windows 2000, so this +won't work on Windows 95/98/ME/NT. + +You'll need MinGW and MSys installed to get this going, available here: + + http://www.mingw.org/ + +At the time of writing, the latest MinGW is 3.1.0-1. If you get this +version, you'll need to also get w32api 2.5 (to get the aforementioned +DNS resolution APIs). Later versions of MinGW should include these. + +Once all this kit is up and running, its business as usual: + + % ./configure + % make + % make install + +Note that you'll still need the various external packages (eg MySQL dev +packages) available in order to build a working server. Getting these up +and running under MinGW is outside the scope of this short guide. + +NOTE WELL: this is not supported by the jabberd project team. If you +have problems, you're welcome to post to jabberd@jabberstudio.org, but +thats all. If you file a bug that can't be reproduced under Unix, then +the bug will be assumed not to exist. If anyone would like to step up to +maintain the port properly, please contact me. + +Huge thanks go out Peter Hinz for the original port. I've cannibalised it +pretty seriously, but I wouldn't have had a chance if I hadn't seen his +code. + +-- Robert Norris , 2004-04-15. diff --git a/TODO b/TODO new file mode 100644 index 00000000..a6c04848 --- /dev/null +++ b/TODO @@ -0,0 +1,64 @@ +TODO for 2.0 +------------ + +Roster templates may not be working properly (esp. with multiple items). +Check and fix if necessary. + + +Make sure that available presence in response to a s10n accept arrives +after the type='subscribed' (see chat with Nathan Walp) + + +c2s may not be properly detecting client connections dropping. I feel +like I've checked this numerous times, so check once more, once and for +all. + + +Modify user-load chain calls to indicate if the user is being loaded for +a session, or just for delivery. Don't load rosters (and other large +things) in that case. + +========== + + The requirement for this is that large presence broadcasts (for the + same host) result in many users being loaded for delivery, only to + find out that they have no sessions online, and then being unloaded + again. If we could stop some parts of the user data being loaded when + the user is loaded only for packet delivery, then the overhead could + be kept to a minimum. + + Privacy lists make this hard, if not impossible. Privacy lists must be + loaded at packet delivery time, because the user may have a default + list. That default list may have rules based on roster group, so the + roster must be loaded. + + A partial solution might be to have a module that runs in in-router + before mod_privacy, that watches for presence packets and drops them + if the user is not online. This should work, because even if the + presence packet was allowed by the default privacy list, it'll get + dropped anyway because mod_deliver would find later that there are no + sessions online. + + This doesn't fix the problem completely though because mod_privacy + needs the user's roster loaded in order to process a default list with + rules based on roster group. There's no way for mod_privacy to call + into mod_roster to get it to load the roster on demand. + + Long term, there's a couple of possibilities. In general, I believe + its reasonable to either have all user data loaded, or none of it - + anything in between requires a measure of the "importance" of certain + data (which doesn't scale and is highly subjective) or a on-demand + system of loading data, which requires dependency graphs and + inter-module communication. This stuff isn't impossible, but I don't + want to go there if I can help it. + + It might be better to simply implement a (configurable) delay before + unused user data is unloaded. This will still mean that the first + presence broadcast can get sluggish, but after this things should chug + along nicely. + + A patch for the partial soltuion mentioned above has been implemented, + as _presence_in_router(). This is not a complete fix, but does + workaround the most common case. + +========== diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 00000000..c9e5796f --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,499 @@ +dnl ******** AC_COMPILE_CHECK_SIZEOF +dnl Available from the GNU Autoconf Macro Archive at: +dnl http://www.gnu.org/software/ac-archive/htmldoc/ac_compile_check_sizeof.html +dnl +AC_DEFUN([AC_COMPILE_CHECK_SIZEOF], +[changequote(<<, >>)dnl +dnl The name to #define. +define(<>, translit(sizeof_$1, [a-z *], [A-Z_P]))dnl +dnl The cache variable name. +define(<>, translit(ac_cv_sizeof_$1, [ *], [_p]))dnl +changequote([, ])dnl +AC_MSG_CHECKING(size of $1) +AC_CACHE_VAL(AC_CV_NAME, +[for ac_size in 4 8 1 2 16 $2 ; do # List sizes in rough order of prevalence. + AC_TRY_COMPILE([#include "confdefs.h" +#include +$2 +], [switch (0) case 0: case (sizeof ($1) == $ac_size):;], AC_CV_NAME=$ac_size) + if test x$AC_CV_NAME != x ; then break; fi +done +]) +if test x$AC_CV_NAME = x ; then + AC_MSG_ERROR([cannot determine a size for $1]) +fi +AC_MSG_RESULT($AC_CV_NAME) +AC_DEFINE_UNQUOTED(AC_TYPE_NAME, $AC_CV_NAME, [The number of bytes in type $1]) +undefine([AC_TYPE_NAME])dnl +undefine([AC_CV_NAME])dnl +]) + +dnl ******** AC_CREATE_STDINT_H +dnl Available from the GNU Autoconf Macro Archive at: +dnl http://www.gnu.org/software/ac-archive/htmldoc/ac_create_stdint_h.html +dnl +AC_DEFUN([AC_CREATE_STDINT_H], +[# ------ AC CREATE STDINT H ------------------------------------- +AC_MSG_CHECKING([for stdint-types....]) +ac_stdint_h=`echo ifelse($1, , _stdint.h, $1)` +if test "$ac_stdint_h" = "stdint.h" ; then + AC_MSG_RESULT("(are you sure you want them in ./stdint.h?)") +elif test "$ac_stdint_h" = "inttypes.h" ; then + AC_MSG_RESULT("(are you sure you want them in ./inttypes.h?)") +else + AC_MSG_RESULT("(putting them into $ac_stdint_h)") +fi + +inttype_headers=`echo inttypes.h sys/inttypes.h sys/inttypes.h $2 \ +| sed -e 's/,/ /g'` + + ac_cv_header_stdint_x="no-file" + ac_cv_header_stdint_o="no-file" + ac_cv_header_stdint_u="no-file" + for i in stdint.h $inttype_headers ; do + unset ac_cv_type_uintptr_t + unset ac_cv_type_uint64_t + _AC_CHECK_TYPE_NEW(uintptr_t,[ac_cv_header_stdint_x=$i],dnl + continue,[#include <$i>]) + AC_CHECK_TYPE(uint64_t,[and64="(uint64_t too)"],[and64=""],[#include<$i>]) + AC_MSG_RESULT(... seen our uintptr_t in $i $and64) + break; + done + if test "$ac_cv_header_stdint_x" = "no-file" ; then + for i in stdint.h $inttype_headers ; do + unset ac_cv_type_uint32_t + unset ac_cv_type_uint64_t + AC_CHECK_TYPE(uint32_t,[ac_cv_header_stdint_o=$i],dnl + continue,[#include <$i>]) + AC_CHECK_TYPE(uint64_t,[and64="(uint64_t too)"],[and64=""],[#include<$i>]) + AC_MSG_RESULT(... seen our uint32_t in $i $and64) + break; + done + if test "$ac_cv_header_stdint_o" = "no-file" ; then + for i in sys/types.h $inttype_headers ; do + unset ac_cv_type_u_int32_t + unset ac_cv_type_u_int64_t + AC_CHECK_TYPE(u_int32_t,[ac_cv_header_stdint_u=$i],dnl + continue,[#include <$i>]) + AC_CHECK_TYPE(uint64_t,[and64="(u_int64_t too)"],[and64=""],[#include<$i>]) + AC_MSG_RESULT(... seen our u_int32_t in $i $and64) + break; + done + fi + fi + +# ----------------- DONE inttypes.h checks MAYBE C basic types -------- + +if test "$ac_cv_header_stdint_x" = "no-file" ; then + AC_COMPILE_CHECK_SIZEOF(char) + AC_COMPILE_CHECK_SIZEOF(short) + AC_COMPILE_CHECK_SIZEOF(int) + AC_COMPILE_CHECK_SIZEOF(long) + AC_COMPILE_CHECK_SIZEOF(void*) + ac_cv_header_stdint_test="yes" +else + ac_cv_header_stdint_test="no" +fi + +# ----------------- DONE inttypes.h checks START header ------------- +_ac_stdint_h=AS_TR_CPP(_$ac_stdint_h) +AC_MSG_RESULT(creating $ac_stdint_h : $_ac_stdint_h) +echo "#ifndef" $_ac_stdint_h >$ac_stdint_h +echo "#define" $_ac_stdint_h "1" >>$ac_stdint_h +echo "#ifndef" _GENERATED_STDINT_H >>$ac_stdint_h +echo "#define" _GENERATED_STDINT_H '"'$PACKAGE $VERSION'"' >>$ac_stdint_h +if test "$GCC" = "yes" ; then + echo "/* generated using a gnu compiler version" `$CC --version` "*/" \ + >>$ac_stdint_h +else + echo "/* generated using $CC */" >>$ac_stdint_h +fi +echo "" >>$ac_stdint_h + +if test "$ac_cv_header_stdint_x" != "no-file" ; then + ac_cv_header_stdint="$ac_cv_header_stdint_x" +elif test "$ac_cv_header_stdint_o" != "no-file" ; then + ac_cv_header_stdint="$ac_cv_header_stdint_o" +elif test "$ac_cv_header_stdint_u" != "no-file" ; then + ac_cv_header_stdint="$ac_cv_header_stdint_u" +else + ac_cv_header_stdint="stddef.h" +fi + +# ----------------- See if int_least and int_fast types are present +unset ac_cv_type_int_least32_t +unset ac_cv_type_int_fast32_t +AC_CHECK_TYPE(int_least32_t,,,[#include <$ac_cv_header_stdint>]) +AC_CHECK_TYPE(int_fast32_t,,,[#include<$ac_cv_header_stdint>]) + +if test "$ac_cv_header_stdint" != "stddef.h" ; then +if test "$ac_cv_header_stdint" != "stdint.h" ; then +AC_MSG_RESULT(..adding include stddef.h) + echo "#include " >>$ac_stdint_h +fi ; fi +AC_MSG_RESULT(..adding include $ac_cv_header_stdint) + echo "#include <$ac_cv_header_stdint>" >>$ac_stdint_h +echo "" >>$ac_stdint_h + +# ----------------- DONE header START basic int types ------------- +if test "$ac_cv_header_stdint_x" = "no-file" ; then + AC_MSG_RESULT(... need to look at C basic types) +dnl ac_cv_header_stdint_test="yes" # moved up before creating the file +else + AC_MSG_RESULT(... seen good stdint.h inttypes) +dnl ac_cv_header_stdint_test="no" # moved up before creating the file +fi + +if test "$ac_cv_header_stdint_u" != "no-file" ; then + AC_MSG_RESULT(... seen bsd/sysv typedefs) + cat >>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h < 199901L + +#ifndef _HAVE_UINT64_T +#define _HAVE_UINT64_T +typedef long long int64_t; +typedef unsigned long long uint64_t; +#endif + +#elif !defined __STRICT_ANSI__ +#if defined _MSC_VER || defined __WATCOMC__ || defined __BORLANDC__ + +#ifndef _HAVE_UINT64_T +#define _HAVE_UINT64_T +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#endif + +#elif defined __GNUC__ || defined __MWERKS__ || defined __ELF__ +dnl /* note: all ELF-systems seem to have loff-support which needs 64-bit */ + +#if !defined _NO_LONGLONG +#ifndef _HAVE_UINT64_T +#define _HAVE_UINT64_T +typedef long long int64_t; +typedef unsigned long long uint64_t; +#endif +#endif + +#elif defined __alpha || (defined __mips && defined _ABIN32) + +#if !defined _NO_LONGLONG +#ifndef _HAVE_UINT64_T +#define _HAVE_UINT64_T +typedef long int64_t; +typedef unsigned long uint64_t; +#endif +#endif + /* compiler/cpu type ... or just ISO C99 */ +#endif +#endif +EOF + +# plus a default 64-bit for systems that are likely to be 64-bit ready + case "$ac_cv_sizeof_x:$ac_cv_sizeof_voidp:$ac_cv_sizeof_long" in + 1:2:8:8) AC_MSG_RESULT(..adding uint64_t default, normal 64-bit system) +cat >>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <>$ac_stdint_h <&1|head -n 1|egrep '2.5'`"; then + echo "Autoconf 2.50+ is required. Aborting build..."; + exit 1; +fi + +if test -z "`automake --version 2>&1|head -n 1|egrep '1.[4-9]'`"; then + echo "Automake 1.4+ is required. Aborting build..."; + exit 1; +fi + +if test -z "`libtool --version 2>&1|head -n 1|egrep '1.[45]'`"; then + echo "Libtool 1.4+ is required. Aborting build..."; + exit 1; +fi + +# Fire up autotools +libtoolize --force && aclocal $ACLOCAL_FLAGS && autoheader && automake --include-deps --add-missing && autoconf diff --git a/c2s/.cvsignore b/c2s/.cvsignore new file mode 100644 index 00000000..f9841cac --- /dev/null +++ b/c2s/.cvsignore @@ -0,0 +1,5 @@ +Makefile +Makefile.in +.deps +.libs +c2s diff --git a/c2s/Makefile.am b/c2s/Makefile.am new file mode 100644 index 00000000..d49bc2f3 --- /dev/null +++ b/c2s/Makefile.am @@ -0,0 +1,12 @@ +INCLUDES = -DCONFIG_DIR=\"$(sysconfdir)\" + +bin_PROGRAMS = c2s + +c2s_SOURCES = authreg.c authreg_anon.c authreg_db.c authreg_ldap.c authreg_mysql.c authreg_pam.c authreg_pgsql.c authreg_pipe.c bind.c c2s.c main.c sm.c +noinst_HEADERS = c2s.h + +c2s_LDADD = $(top_builddir)/sx/libsx.la \ + $(top_builddir)/mio/libmio.la \ + $(top_builddir)/util/libutil.la \ + $(top_builddir)/subst/libsubst.la \ + $(top_builddir)/expat/libexpat.la diff --git a/c2s/authreg.c b/c2s/authreg.c new file mode 100644 index 00000000..fd44d11b --- /dev/null +++ b/c2s/authreg.c @@ -0,0 +1,766 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "c2s.h" + +#ifdef HAVE_IDN +#include +#endif + +/* authreg module manager */ + +/* if you add a module, you'll need to update these arrays */ + +#ifdef STORAGE_MYSQL +extern int ar_mysql_init(authreg_t); +#endif +#ifdef STORAGE_PGSQL +extern int ar_pgsql_init(authreg_t); +#endif +#ifdef STORAGE_DB +extern int ar_db_init(authreg_t); +#endif +#ifdef STORAGE_LDAP +extern int ar_ldap_init(authreg_t); +#endif +#ifdef STORAGE_PAM +extern int ar_pam_init(authreg_t); +#endif +#ifdef STORAGE_PIPE +extern int ar_pipe_init(authreg_t); +#endif +#ifdef STORAGE_ANON +extern int ar_anon_init(authreg_t); +#endif + +char *module_names[] = { +#ifdef STORAGE_MYSQL + "mysql", +#endif +#ifdef STORAGE_PGSQL + "pgsql", +#endif +#ifdef STORAGE_DB + "db", +#endif +#ifdef STORAGE_LDAP + "ldap", +#endif +#ifdef STORAGE_PAM + "pam", +#endif +#ifdef STORAGE_PIPE + "pipe", +#endif +#ifdef STORAGE_ANON + "anon", +#endif + NULL +}; + +ar_module_init_fn module_inits[] = { +#ifdef STORAGE_MYSQL + ar_mysql_init, +#endif +#ifdef STORAGE_PGSQL + ar_pgsql_init, +#endif +#ifdef STORAGE_DB + ar_db_init, +#endif +#ifdef STORAGE_LDAP + ar_ldap_init, +#endif +#ifdef STORAGE_PAM + ar_pam_init, +#endif +#ifdef STORAGE_PIPE + ar_pipe_init, +#endif +#ifdef STORAGE_ANON + ar_anon_init, +#endif + NULL +}; + +typedef struct _authreg_error_st { + char *class; + char *name; + char *code; + char *uri; +} *authreg_error_t; + +/** get a handle for the named module */ +authreg_t authreg_init(c2s_t c2s, char *name) { + int n; + ar_module_init_fn init = NULL; + authreg_t ar; + + /* hunt it down */ + n = 0; + while(module_names[n] != NULL) + { + if(strcmp(module_names[n], name) == 0) + { + init = module_inits[n]; + break; + } + n++; + } + + if(init == NULL) + { + log_write(c2s->log, LOG_ERR, "no such auth module '%s'", name); + return NULL; + } + + /* make a new one */ + ar = (authreg_t) malloc(sizeof(struct authreg_st)); + memset(ar, 0, sizeof(struct authreg_st)); + + ar->c2s = c2s; + + /* call the initialiser */ + if((init)(ar) != 0) + { + log_write(c2s->log, LOG_ERR, "failed to initialise auth module '%s'", name); + authreg_free(ar); + return NULL; + } + + /* we need user_exists(), at the very least */ + if(ar->user_exists == NULL) + { + log_write(c2s->log, LOG_ERR, "auth module '%s' has no check for user existence", name); + authreg_free(ar); + return NULL; + } + + /* its good */ + log_write(c2s->log, LOG_NOTICE, "initialised auth module '%s'", name); + + return ar; +} + +/** shutdown the authreg system */ +void authreg_free(authreg_t ar) { + if(ar->free != NULL) (ar->free)(ar); + free(ar); +} + +/** auth get handler */ +static void _authreg_auth_get(c2s_t c2s, sess_t sess, nad_t nad) { + int ns, elem, ssequence, attr; + char username[1024], shash[41], stoken[11], seqs[10], id[128]; + + /* can't auth if they're active */ + if(sess->active) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_NOT_ALLOWED), 0)); + return; + } + + /* sort out the username */ + ns = nad_find_scoped_namespace(nad, "jabber:iq:auth", NULL); + elem = nad_find_elem(nad, 1, ns, "username", 1); + if(elem < 0) + { + log_debug(ZONE, "auth get with no username, bouncing it"); + + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_BAD_REQUEST), 0)); + + return; + } + + snprintf(username, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); +#ifdef HAVE_IDN + if(stringprep_xmpp_nodeprep(username, 1024) != 0) { + log_debug(ZONE, "auth get username failed nodeprep, bouncing it"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_JID_MALFORMED), 0)); + return; + } +#endif + + /* no point going on if we have no mechanisms */ + if(!(c2s->ar_mechanisms & (AR_MECH_TRAD_PLAIN | AR_MECH_TRAD_DIGEST | AR_MECH_TRAD_ZEROK))) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_FORBIDDEN), 0)); + return; + } + + /* do we have the user? */ + if((c2s->ar->user_exists)(c2s->ar, username, sess->realm) == 0) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_OLD_UNAUTH), 0)); + return; + } + + /* extract the id */ + attr = nad_find_attr(nad, 0, -1, "id", NULL); + if(attr >= 0) + snprintf(id, 128, "%.*s", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + + nad_free(nad); + + /* build a result packet */ + nad = nad_new(sess->s->nad_cache); + + ns = nad_add_namespace(nad, uri_CLIENT, NULL); + + nad_append_elem(nad, ns, "iq", 0); + nad_append_attr(nad, -1, "type", "result"); + + if(attr >= 0) + nad_append_attr(nad, -1, "id", id); + + ns = nad_add_namespace(nad, "jabber:iq:auth", NULL); + nad_append_elem(nad, ns, "query", 1); + + nad_append_elem(nad, ns, "username", 2); + nad_append_cdata(nad, username, strlen(username), 3); + + nad_append_elem(nad, ns, "resource", 2); + + /* fill out the packet with available auth mechanisms */ + if(c2s->ar_mechanisms & AR_MECH_TRAD_PLAIN && (c2s->ar->get_password != NULL || c2s->ar->check_password != NULL)) + nad_append_elem(nad, ns, "password", 2); + + if(c2s->ar_mechanisms & AR_MECH_TRAD_DIGEST && c2s->ar->get_password != NULL) + nad_append_elem(nad, ns, "digest", 2); + + /* don't offer zerok if the sequence is zero */ + if(c2s->ar_mechanisms & AR_MECH_TRAD_ZEROK && c2s->ar->get_zerok != NULL && c2s->ar->set_zerok != NULL && (c2s->ar->get_zerok)(c2s->ar, username, sess->realm, shash, stoken, &ssequence) == 0 && ssequence > 0) + { + snprintf(seqs, 10, "%d", ssequence - 1); + nad_append_elem(nad, ns, "sequence", 2); + nad_append_cdata(nad, seqs, strlen(seqs), 3); + + nad_append_elem(nad, ns, "token", 2); + nad_append_cdata(nad, stoken, strlen(stoken), 3); + } + + /* give it back to the client */ + sx_nad_write(sess->s, nad); + + return; +} + +/** auth set handler */ +static void _authreg_auth_set(c2s_t c2s, sess_t sess, nad_t nad) { + int ns, elem, attr, authd = 0, ssequence; + char username[1024], resource[1024], str[1024], shash[41], stoken[11], hash[280]; + + /* can't auth if they're active */ + if(sess->active) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_NOT_ALLOWED), 0)); + return; + } + + ns = nad_find_scoped_namespace(nad, "jabber:iq:auth", NULL); + + /* sort out the username */ + elem = nad_find_elem(nad, 1, ns, "username", 1); + if(elem < 0) + { + log_debug(ZONE, "auth set with no username, bouncing it"); + + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_BAD_REQUEST), 0)); + + return; + } + + snprintf(username, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); +#ifdef HAVE_IDN + if(stringprep_xmpp_nodeprep(username, 1024) != 0) { + log_debug(ZONE, "auth set username failed nodeprep, bouncing it"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_JID_MALFORMED), 0)); + return; + } +#endif + + /* make sure we have the resource */ + elem = nad_find_elem(nad, 1, ns, "resource", 1); + if(elem < 0) + { + log_debug(ZONE, "auth set with no resource, bouncing it"); + + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_BAD_REQUEST), 0)); + + return; + } + + snprintf(resource, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); +#ifdef HAVE_IDN + if(stringprep_xmpp_resourceprep(resource, 1024) != 0) { + log_debug(ZONE, "auth set resource failed resourceprep, bouncing it"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_JID_MALFORMED), 0)); + return; + } +#endif + + /* no point going on if we have no mechanisms */ + if(!(c2s->ar_mechanisms & (AR_MECH_TRAD_PLAIN | AR_MECH_TRAD_DIGEST | AR_MECH_TRAD_ZEROK))) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_FORBIDDEN), 0)); + return; + } + + /* do we have the user? */ + if((c2s->ar->user_exists)(c2s->ar, username, sess->realm) == 0) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_OLD_UNAUTH), 0)); + return; + } + + /* zerok auth */ + if(!authd && c2s->ar_mechanisms & AR_MECH_TRAD_ZEROK && c2s->ar->get_zerok != NULL && c2s->ar->set_zerok != NULL && (c2s->ar->get_zerok)(c2s->ar, username, sess->realm, shash, stoken, &ssequence) == 0) + { + elem = nad_find_elem(nad, 1, ns, "hash", 1); + if(elem >= 0) + { + snprintf(hash, 41, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + shahash_r(hash, hash); + + if(strcmp(hash, shash) == 0) + { + /* update the auth creds */ + ssequence--; + + /* don't auth them if we can't update their auth creds */ + snprintf(str, 41, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + if((c2s->ar->set_zerok)(c2s->ar, username, sess->realm, str, stoken, ssequence) == 0) + { + authd = 1; + log_debug(ZONE, "zerok auth succeeded"); + } + else + log_debug(ZONE, "couldn't update auth creds, not allowing zerok auth"); + } + } + } + + /* digest auth */ + if(!authd && c2s->ar_mechanisms & AR_MECH_TRAD_DIGEST && c2s->ar->get_password != NULL) + { + elem = nad_find_elem(nad, 1, ns, "digest", 1); + if(elem >= 0) + { + if((c2s->ar->get_password)(c2s->ar, username, sess->realm, str) == 0) + { + snprintf(hash, 280, "%s%s", sess->s->id, str); + shahash_r(hash, hash); + + if(strlen(hash) == NAD_CDATA_L(nad, elem) && strncmp(hash, NAD_CDATA(nad, elem), NAD_CDATA_L(nad, elem)) == 0) + { + log_debug(ZONE, "digest auth succeeded"); + authd = 1; + } + } + } + } + + /* plaintext auth (compare) */ + if(!authd && c2s->ar_mechanisms & AR_MECH_TRAD_PLAIN && c2s->ar->get_password != NULL) + { + elem = nad_find_elem(nad, 1, ns, "password", 1); + if(elem >= 0) + { + if((c2s->ar->get_password)(c2s->ar, username, sess->realm, str) == 0 && strlen(str) == NAD_CDATA_L(nad, elem) && strncmp(str, NAD_CDATA(nad, elem), NAD_CDATA_L(nad, elem)) == 0) + { + log_debug(ZONE, "plaintext auth (compare) succeeded"); + authd = 1; + } + } + } + + /* plaintext auth (check) */ + if(!authd && c2s->ar_mechanisms & AR_MECH_TRAD_PLAIN && c2s->ar->check_password != NULL) + { + elem = nad_find_elem(nad, 1, ns, "password", 1); + if(elem >= 0) + { + snprintf(str, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + if((c2s->ar->check_password)(c2s->ar, username, sess->realm, str) == 0) + { + log_debug(ZONE, "plaintext auth (check) succeded"); + authd = 1; + } + } + } + + /* now, are they authenticated? */ + if(authd) + { + log_write(c2s->log, LOG_NOTICE, "[%d] auth succeeded: username=%s, resource=%s", sess->s->tag, username, resource); + + /* our local id */ + sprintf(sess->c2s_id, "%d", sess->s->tag); + + /* the full user jid for this session */ + sess->jid = jid_new(c2s->pc, sess->s->req_to, -1); + jid_reset_components(sess->jid, username, sess->jid->domain, resource); + + log_write(sess->c2s->log, LOG_NOTICE, "[%d] requesting session: jid=%s", sess->s->tag, jid_full(sess->jid)); + + /* build a result packet, we'll send this back to the client after we have a session for them */ + sess->result = nad_new(sess->s->nad_cache); + + ns = nad_add_namespace(sess->result, uri_CLIENT, NULL); + + nad_append_elem(sess->result, ns, "iq", 0); + nad_set_attr(sess->result, 0, -1, "type", "result", 6); + + attr = nad_find_attr(nad, 0, -1, "id", NULL); + if(attr >= 0) + nad_set_attr(sess->result, 0, -1, "id", NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + /* start a session with the sm */ + sm_start(sess); + + /* finished with the nad */ + nad_free(nad); + + return; + } + + log_write(c2s->log, LOG_NOTICE, "[%d] auth failed: username=%s, resource=%s", sess->s->tag, username, resource); + + /* auth failed, so error */ + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_OLD_UNAUTH), 0)); + + return; +} + +/** register get handler */ +static void _authreg_register_get(c2s_t c2s, sess_t sess, nad_t nad) { + int attr, ns; + char id[128]; + + /* registrations can happen if reg is enabled and we can create users and set passwords */ + if(sess->active || !(c2s->ar->set_password != NULL && c2s->ar->create_user != NULL && c2s->ar_register_enable)) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_NOT_ALLOWED), 0)); + return; + } + + /* extract the id */ + attr = nad_find_attr(nad, 0, -1, "id", NULL); + if(attr >= 0) + snprintf(id, 128, "%.*s", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + + nad_free(nad); + + /* build a result packet */ + nad = nad_new(sess->s->nad_cache); + + ns = nad_add_namespace(nad, uri_CLIENT, NULL); + + nad_append_elem(nad, ns, "iq", 0); + nad_append_attr(nad, -1, "type", "result"); + + if(attr >= 0) + nad_append_attr(nad, -1, "id", id); + + ns = nad_add_namespace(nad, "jabber:iq:register", NULL); + nad_append_elem(nad, ns, "query", 1); + + nad_append_elem(nad, ns, "username", 2); + nad_append_elem(nad, ns, "password", 2); + + nad_append_elem(nad, ns, "instructions", 2); + nad_append_cdata(nad, c2s->ar_register_instructions, strlen(c2s->ar_register_instructions), 3); + + /* give it back to the client */ + sx_nad_write(sess->s, nad); +} + +/** register set handler */ +static void _authreg_register_set(c2s_t c2s, sess_t sess, nad_t nad) +{ + int ns = 0, elem, attr, sequence = 500, i; + char username[1024], password[1024], hash[41], token[11], str[51]; + + /* if we're not configured for registration (or pw changes), or we can't set passwords, fail outright */ + if(!(c2s->ar_register_enable || c2s->ar_register_password) || c2s->ar->set_password == NULL) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_NOT_ALLOWED), 0)); + return; + } + + ns = nad_find_scoped_namespace(nad, "jabber:iq:register", NULL); + + /* removals */ + if(sess->active && nad_find_elem(nad, 1, ns, "remove", 1) >= 0) { + /* only if full reg is enabled */ + if(!c2s->ar_register_enable) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_NOT_ALLOWED), 0)); + return; + } + + log_debug(ZONE, "user remove requested"); + + /* make sure we can delete them */ + if(c2s->ar->delete_user == NULL) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_NOT_ALLOWED), 0)); + return; + } + + /* otherwise, delete them */ + if((c2s->ar->delete_user)(c2s->ar, sess->jid->node, sess->realm) != 0) { + log_debug(ZONE, "user delete failed"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_INTERNAL_SERVER_ERROR), 0)); + return; + } + + log_write(c2s->log, LOG_NOTICE, "[%d] deleted user: user=%s; realm=%s", sess->s->tag, sess->jid->node, sess->realm); + + log_write(c2s->log, LOG_NOTICE, "[%d] registration remove succeeded, requesting user deletion: jid=%s", sess->s->tag, jid_user(sess->jid)); + + /* make a result nad */ + sess->result = nad_new(sess->s->nad_cache); + + ns = nad_add_namespace(sess->result, uri_CLIENT, NULL); + + nad_append_elem(sess->result, ns, "iq", 0); + nad_set_attr(sess->result, 0, -1, "type", "result", 6); + + /* extract the id */ + attr = nad_find_attr(nad, 0, -1, "id", NULL); + if(attr >= 0) + nad_set_attr(sess->result, 0, -1, "id", NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + nad_free(nad); + + sx_nad_write(sess->s, sess->result); + sess->result = NULL; + + /* get the sm to delete them (it will force their sessions to end) */ + sm_delete(sess); + + return; + } + + /* username is required */ + elem = nad_find_elem(nad, 1, ns, "username", 1); + if(elem < 0) + { + log_debug(ZONE, "register set with no username, bouncing it"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_BAD_REQUEST), 0)); + return; + } + + snprintf(username, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); +#ifdef HAVE_IDN + if(stringprep_xmpp_nodeprep(username, 1024) != 0) { + log_debug(ZONE, "register set username failed nodeprep, bouncing it"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_JID_MALFORMED), 0)); + return; + } +#endif + + elem = nad_find_elem(nad, 1, ns, "password", 1); + if(elem < 0) + { + log_debug(ZONE, "register set with no password, bouncing it"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_BAD_REQUEST), 0)); + return; + } + + /* if they're already auth'd, its a password change */ + if(sess->active) + { + /* confirm that the username matches their auth id */ + if(strcmp(username, sess->jid->node) != 0) + { + log_debug(ZONE, "%s is trying to change password for %s, bouncing it", jid_full(sess->jid), username); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_OLD_UNAUTH), 0)); + return; + } + } + + /* can't go on if we're not doing full reg */ + else if(!c2s->ar_register_enable) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_NOT_ALLOWED), 0)); + return; + } + + /* if they exist, bounce */ + else if((c2s->ar->user_exists)(c2s->ar, username, sess->realm)) + { + log_debug(ZONE, "attempt to register %s, but they already exist", username); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_CONFLICT), 0)); + return; + } + + /* make sure we can create them */ + else if(c2s->ar->create_user == NULL) + { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_NOT_ALLOWED), 0)); + return; + } + + /* otherwise, create them */ + else if((c2s->ar->create_user)(c2s->ar, username, sess->realm) != 0) + { + log_debug(ZONE, "user create failed"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_INTERNAL_SERVER_ERROR), 0)); + return; + } + + else + log_write(c2s->log, LOG_NOTICE, "[%d] created user: user=%s; realm=%s", sess->s->tag, username, sess->realm); + + /* extract the password */ + snprintf(password, 257, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + + /* change it */ + if((c2s->ar->set_password)(c2s->ar, username, sess->realm, password) != 0) + { + log_debug(ZONE, "password store failed"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_INTERNAL_SERVER_ERROR), 0)); + return; + } + + /* store zerok data if we can */ + if(c2s->ar_mechanisms & AR_MECH_TRAD_ZEROK && c2s->ar->set_zerok != NULL) + { + snprintf(token, 11, "%X", (unsigned int) time(NULL)); + + shahash_r(password, hash); + snprintf(str, 51, "%s%s", hash, token); + shahash_r(str, hash); + + for(i = 0; i < sequence; i++) + shahash_r(hash, hash); + + hash[40] = '\0'; + + if((c2s->ar->set_zerok)(c2s->ar, username, sess->realm, hash, token, sequence) != 0) + { + log_debug(ZONE, "zerok store failed"); + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_INTERNAL_SERVER_ERROR), 0)); + return; + } + } + + log_debug(ZONE, "updated auth creds for %s", username); + + /* make a result nad */ + sess->result = nad_new(sess->s->nad_cache); + + ns = nad_add_namespace(sess->result, uri_CLIENT, NULL); + + nad_append_elem(sess->result, ns, "iq", 0); + nad_set_attr(sess->result, 0, -1, "type", "result", 6); + + /* extract the id */ + attr = nad_find_attr(nad, 0, -1, "id", NULL); + if(attr >= 0) + nad_set_attr(sess->result, 0, -1, "id", NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + /* if they're active, then this was just a password change, and we're done */ + if(sess->active) { + log_write(c2s->log, LOG_NOTICE, "[%d] password changed: jid=%s", sess->s->tag, jid_user(sess->jid)); + sx_nad_write(sess->s, sess->result); + sess->result = NULL; + return; + } + + /* our local id */ + sprintf(sess->c2s_id, "%d", sess->s->tag); + + /* the user jid for this transaction */ + sess->jid = jid_new(c2s->pc, sess->s->req_to, -1); + jid_reset_components(sess->jid, username, sess->jid->domain, sess->jid->resource); + + log_write(c2s->log, LOG_NOTICE, "[%d] registration succeeded, requesting user creation: jid=%s", sess->s->tag, jid_user(sess->jid)); + + /* get the sm to create them */ + sm_create(sess); + + nad_free(nad); + + return; +} + +/** + * processor for iq:auth and iq:register packets + * return 0 if handled, 1 if not handled + */ +int authreg_process(c2s_t c2s, sess_t sess, nad_t nad) { + int ns, query, type, authreg = -1, getset = -1; + + /* need iq */ + if(NAD_ENAME_L(nad, 0) != 2 || strncmp("iq", NAD_ENAME(nad, 0), 2) != 0) + return 1; + + /* only want auth or register packets */ + if((ns = nad_find_scoped_namespace(nad, "jabber:iq:auth", NULL)) >= 0 && (query = nad_find_elem(nad, 0, ns, "query", 1)) >= 0) + authreg = 0; + else if((ns = nad_find_scoped_namespace(nad, "jabber:iq:register", NULL)) >= 0 && (query = nad_find_elem(nad, 0, ns, "query", 1)) >= 0) + authreg = 1; + else + return 1; + + /* if its to someone else, pass it */ + if(nad_find_attr(nad, 0, -1, "to", NULL) >= 0 && nad_find_attr(nad, 0, -1, "to", sess->s->req_to) < 0) + return 1; + + /* need a type */ + if((type = nad_find_attr(nad, 0, -1, "type", NULL)) < 0 || NAD_AVAL_L(nad, type) != 3) + { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_BAD_REQUEST), 0)); + return 0; + } + + /* get or set? */ + if(strncmp("get", NAD_AVAL(nad, type), NAD_AVAL_L(nad, type)) == 0) + getset = 0; + else if(strncmp("set", NAD_AVAL(nad, type), NAD_AVAL_L(nad, type)) == 0) + getset = 1; + else + { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_BAD_REQUEST), 0)); + return 0; + } + + /* hand to the correct handler */ + if(authreg == 0) { + /* can't do iq:auth after sasl auth */ + if(sess->sasl_authd) { + sx_nad_write(sess->s, stanza_tofrom(stanza_error(nad, 0, stanza_err_NOT_ALLOWED), 0)); + return 0; + } + + if(getset == 0) { + log_debug(ZONE, "auth get"); + _authreg_auth_get(c2s, sess, nad); + } else if(getset == 1) { + log_debug(ZONE, "auth set"); + _authreg_auth_set(c2s, sess, nad); + } + } + + if(authreg == 1) { + if(getset == 0) { + log_debug(ZONE, "register get"); + _authreg_register_get(c2s, sess, nad); + } else if(getset == 1) { + log_debug(ZONE, "register set"); + _authreg_register_set(c2s, sess, nad); + } + } + + /* handled */ + return 0; +} diff --git a/c2s/authreg_anon.c b/c2s/authreg_anon.c new file mode 100644 index 00000000..6f26a6e3 --- /dev/null +++ b/c2s/authreg_anon.c @@ -0,0 +1,51 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* this is a simple anonymous plugin. It uses the check_password method to + * force authentication to succeed regardless of what credentials the client + * provides + */ + +#include "c2s.h" + +#ifdef STORAGE_ANON + +static int _ar_anon_user_exists(authreg_t ar, char *username, char *realm) +{ + /* always exists */ + return 1; +} + +static int _ar_anon_check_password(authreg_t ar, char *username, char *realm, char password[257]) +{ + /* always correct */ + return 0; +} + +/** start me up */ +int ar_anon_init(authreg_t ar) +{ + ar->user_exists = _ar_anon_user_exists; + ar->check_password = _ar_anon_check_password; + + return 0; +} + +#endif diff --git a/c2s/authreg_db.c b/c2s/authreg_db.c new file mode 100644 index 00000000..03425ed1 --- /dev/null +++ b/c2s/authreg_db.c @@ -0,0 +1,396 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* this module uses the Berkeley DB v4 (db4) to store the auth credentials */ + +/* + * !!! we must catch DB_RUNRECOVERY and call _ar_db_panic(). I would argue that + * Berkeley should do this for all cases, not just for the process that + * caused the fault, but I'm not sure they see it that way. (I have asked, + * just waiting for a reply) + * + * Sleepycat SR#7019 resolved this. There is an unreleased patch available + * (I have a copy) that will be in 4.2 (due in June). + */ + +#include "c2s.h" + +#ifdef STORAGE_DB + +#include + +/** internal structure, holds auth credentials for one user */ +typedef struct creds_st +{ + char username[257]; + char realm[257]; + char password[257]; + char token[11]; + int sequence; + char hash[41]; +} *creds_t; + +/** internal structure, holds our data */ +typedef struct moddata_st +{ + DB_ENV *env; + + char *path; + int sync; + + xht realms; + DB *def_realm; +} *moddata_t; + +/** open/create the database for this realm */ +static DB *_ar_db_get_realm_db(authreg_t ar, char *realm) +{ + moddata_t data = (moddata_t) ar->private; + DB *db; + int err; + + if(realm[0] == '\0') + db = data->def_realm; + else + db = xhash_get(data->realms, realm); + if(db != NULL) + return db; + + log_debug(ZONE, "creating new db handle for realm '%s'", realm); + + err = db_create(&db, data->env, 0); + if(err != 0) + { + log_write(ar->c2s->log, LOG_ERR, "db: couldn't create db: %s", db_strerror(err)); + return NULL; + } + + err = db->open(db, NULL, "authreg.db", realm, DB_HASH, DB_CREATE, 0); + if(err != 0) + { + log_write(ar->c2s->log, LOG_ERR, "db: couldn't open db for realm '%s': %s", realm, db_strerror(err)); + db->close(db, 0); + return NULL; + } + + if(realm[0] == '\0') + data->def_realm = db; + else + xhash_put(data->realms, pstrdup(xhash_pool(data->realms), realm), (void *) db); + + log_debug(ZONE, "db for realm '%s' is online", realm); + + return db; +} + +/** pull a user out of the db */ +static creds_t _ar_db_fetch_user(authreg_t ar, char *username, char *realm) +{ + DB *db; + DBT key, val; + int err; + creds_t creds; + + log_debug(ZONE, "fetching auth creds for user '%s' realm '%s'", username, realm); + + db = _ar_db_get_realm_db(ar, realm); + if(db == NULL) + return NULL; + + memset(&key, 0, sizeof(DBT)); + memset(&val, 0, sizeof(DBT)); + + key.data = username; + key.size = strlen(username); + + err = db->get(db, NULL, &key, &val, 0); + if(err == 0) + creds = (creds_t) val.data; + else if(err == DB_NOTFOUND) + creds = NULL; + else + { + log_write(ar->c2s->log, LOG_ERR, "db: couldn't fetch auth creds for user '%s' (realm '%s'): %s", username, realm, db_strerror(err)); + return NULL; + } + + log_debug(ZONE, "auth creds: %X", creds); + + return creds; +} + +/** store the user into the db */ +static int _ar_db_store_user(authreg_t ar, creds_t creds) +{ + moddata_t data = (moddata_t) ar->private; + DB *db; + DBT key, val; + int err; + + log_debug(ZONE, "storing auth creds for user '%s' realm '%s'", creds->username, creds->realm); + + db = _ar_db_get_realm_db(ar, creds->realm); + if(db == NULL) + return 1; + + memset(&key, 0, sizeof(DBT)); + memset(&val, 0, sizeof(DBT)); + + key.data = creds->username; + key.size = strlen(creds->username); + + val.data = creds; + val.size = sizeof(struct creds_st); + + err = db->put(db, NULL, &key, &val, 0); + if(err != 0) + { + log_write(ar->c2s->log, LOG_ERR, "db: couldn't store auth creds for user '%s' (realm '%s'): %s", creds->username, creds->realm, db_strerror(err)); + return 1; + } + + if(data->sync) + db->sync(db, 0); + + return 0; +} + +static int _ar_db_user_exists(authreg_t ar, char *username, char *realm) +{ + return (int) _ar_db_fetch_user(ar, username, realm); +} + +static int _ar_db_get_password(authreg_t ar, char *username, char *realm, char password[257]) +{ + creds_t creds; + + if((creds = _ar_db_fetch_user(ar, username, realm)) == NULL) + return 1; + + strcpy(password, creds->password); + + return 0; +} + +static int _ar_db_set_password(authreg_t ar, char *username, char *realm, char password[257]) +{ + creds_t creds; + + if((creds = _ar_db_fetch_user(ar, username, realm)) == NULL) + return 1; + + strcpy(creds->password, password); + + if(_ar_db_store_user(ar, creds) != 0) + return 1; + + return 0; +} + +static int _ar_db_get_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int *sequence) +{ + creds_t creds; + + if((creds = _ar_db_fetch_user(ar, username, realm)) == NULL) + return 1; + + strcpy(hash, creds->hash); + strcpy(token, creds->token); + *sequence = creds->sequence; + + return 0; +} + +static int _ar_db_set_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int sequence) +{ + creds_t creds; + + if((creds = _ar_db_fetch_user(ar, username, realm)) == NULL) + return 1; + + strcpy(creds->hash, hash); + strcpy(creds->token, token); + creds->sequence = sequence; + + if(_ar_db_store_user(ar, creds) != 0) + return 1; + + return 0; +} + +static int _ar_db_create_user(authreg_t ar, char *username, char *realm) +{ + creds_t creds; + int ret; + + if((creds = _ar_db_fetch_user(ar, username, realm)) != NULL) + return 1; + + creds = (creds_t) malloc(sizeof(struct creds_st)); + memset(creds, 0, sizeof(struct creds_st)); + + strcpy(creds->username, username); + strcpy(creds->realm, realm); + + ret = _ar_db_store_user(ar, creds); + + free(creds); + return ret; +} + +static int _ar_db_delete_user(authreg_t ar, char *username, char *realm) +{ + DB *db; + DBT key; + int err; + + if(_ar_db_fetch_user(ar, username, realm) == NULL) + return 1; + + db = _ar_db_get_realm_db(ar, realm); + if(db == NULL) + return 1; + + memset(&key, 0, sizeof(DBT)); + + key.data = username; + key.size = strlen(username); + + err = db->del(db, NULL, &key, 0); + if(err != 0) + log_write(ar->c2s->log, LOG_ERR, "db: couldn't delete auth creds for user '%s' (realm '%s'): %s", username, realm, db_strerror(err)); + + return err; +} + +static void _ar_db_free_walker(xht realms, const char *realm, void *val, void *arg) +{ + DB *db = (DB *) val; + + log_debug(ZONE, "closing '%s' db", realm); + + db->close(db, 0); +} + +static void _ar_db_free(authreg_t ar) +{ + DB_ENV *env; + + moddata_t data = (moddata_t) ar->private; + + log_debug(ZONE, "db module shutting down"); + + xhash_walk(data->realms, _ar_db_free_walker, NULL); + + xhash_free(data->realms); + + data->env->close(data->env, 0); + + /* remove db environment files if no longer required */ + if (db_env_create(&env, 0) == 0) + env->remove(env, data->path, 0); + + free(data); +} + +/** panic function */ +static void _ar_db_panic(DB_ENV *env, int errval) +{ + log_t log = (log_t) env->app_private; + + log_write(log, LOG_CRIT, "db: corruption detected! close all jabberd processes and run db_recover"); + + exit(2); +} + +/** start me up */ +int ar_db_init(authreg_t ar) +{ + char *path; + int err; + DB_ENV *env; + moddata_t data; + + path = config_get_one(ar->c2s->config, "authreg.db.path", 0); + if(path == NULL) + { + log_write(ar->c2s->log, LOG_ERR, "db: no authreg path specified in config file"); + return 1; + } + + err = db_env_create(&env, 0); + if(err != 0) + { + log_write(ar->c2s->log, LOG_ERR, "db: couldn't create environment: %s", db_strerror(err)); + return 1; + } + + err = env->set_paniccall(env, _ar_db_panic); + if(err != 0) + { + log_write(ar->c2s->log, LOG_ERR, "db: couldn't set panic call: %s", db_strerror(err)); + return 1; + } + + /* store the log context in case we panic */ + env->app_private = ar->c2s->log; + + err = env->set_flags(env, DB_AUTO_COMMIT, 1); + if(err != 0) + { + log_write(ar->c2s->log, LOG_ERR, "db: couldn't set environment for automatic transaction commit: %s", db_strerror(err)); + env->close(env, 0); + return 1; + } + + err = env->open(env, path, DB_INIT_LOCK | DB_INIT_MPOOL | DB_INIT_LOG | DB_INIT_TXN | DB_CREATE, 0); + if(err != 0) + { + log_write(ar->c2s->log, LOG_ERR, "db: couldn't open environment: %s", db_strerror(err)); + env->close(env, 0); + return 1; + } + + data = (moddata_t) malloc(sizeof(struct moddata_st)); + memset(data, 0, sizeof(struct moddata_st)); + + data->env = env; + data->path = path; + + if(config_get_one(ar->c2s->config, "authreg.db.sync", 0) != NULL) + data->sync = 1; + + data->realms = xhash_new(51); + + ar->private = data; + + ar->user_exists = _ar_db_user_exists; + ar->get_password = _ar_db_get_password; + ar->set_password = _ar_db_set_password; + ar->get_zerok = _ar_db_get_zerok; + ar->set_zerok = _ar_db_set_zerok; + ar->create_user = _ar_db_create_user; + ar->delete_user = _ar_db_delete_user; + ar->free = _ar_db_free; + + return 0; +} + +#endif diff --git a/c2s/authreg_ldap.c b/c2s/authreg_ldap.c new file mode 100644 index 00000000..d2e00f8b --- /dev/null +++ b/c2s/authreg_ldap.c @@ -0,0 +1,317 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* this plugin authenticates against an LDAP directory by attempting to bind + * as the user. It won't store or retrieve any actual data, so only the + * plaintext mechanism is available. + * + * !!! this doesn't do any caching. It really should. + * + * !!! this blocks for every auth. We're stuck with this until authreg can + * return a pending state. The timeout helps, but its still icky. + */ + +#include "c2s.h" + +#ifdef STORAGE_LDAP + +#include +#include + +#define AR_LDAP_FLAGS_NONE (0x0) +#define AR_LDAP_FLAGS_STARTTLS (0x1) +#define AR_LDAP_FLAGS_SSL (0x2) +#define AR_LDAP_FLAGS_V3 (0x4) + +/** internal structure, holds our data */ +typedef struct moddata_st +{ + authreg_t ar; + + LDAP *ld; + + char *host; + long port; + + int flags; + + char *binddn; + char *bindpw; + + char *uidattr; + + xht basedn; + char *default_basedn; +} *moddata_t; + +/** utility function to get ld_errno */ +static int _ldap_get_lderrno(LDAP *ld) +{ + int ld_errno; + + ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ld_errno); + + return ld_errno; +} + +/** connect to the ldap host */ +static int _ldap_connect(moddata_t data) +{ + char url[1024]; + int version = (data->flags & AR_LDAP_FLAGS_V3) ? 3 : 2; + + /* ssl "wrappermode" */ + if(data->flags & AR_LDAP_FLAGS_SSL) { + snprintf(url, sizeof(url), "ldaps://%s:%d", data->host, data->port); + ldap_initialize(&data->ld, url); + } + /* non-SSL connect method */ + else + data->ld = ldap_init(data->host, data->port); + + if(data->ld != NULL) { + /* explicitly set ldap version for all connections */ + if(ldap_set_option(data->ld, LDAP_OPT_PROTOCOL_VERSION, &version)) { + log_write(data->ar->c2s->log, LOG_ERR, "ldap: couldn't use version %d: %s", version, ldap_err2string(_ldap_get_lderrno(data->ld))); + ldap_unbind_s(data->ld); + data->ld = NULL; + return 1; + } + + /* starttls */ + if(data->flags & AR_LDAP_FLAGS_STARTTLS) { + if(ldap_start_tls_s(data->ld, NULL, NULL)) { + log_write(data->ar->c2s->log, LOG_ERR, "ldap: couldn't start TLS: %s", ldap_err2string(_ldap_get_lderrno(data->ld))); + ldap_unbind_s(data->ld); + data->ld = NULL; + return 1; + } + } + } else { + log_write(data->ar->c2s->log, LOG_ERR, "ldap: connect to server at %s:%d failed", data->host, data->port); + return 1; + } + + return 0; +} + +/** do a search, return the dn */ +static char *_ldap_search(moddata_t data, char *realm, char *username) +{ + char filter[1024], *dn, *no_attrs[] = { NULL }, *basedn; + LDAPMessage *result, *entry; + + basedn = xhash_get(data->basedn, realm); + if(basedn == NULL) + basedn = data->default_basedn; + + if(basedn == NULL) { + log_write(data->ar->c2s->log, LOG_ERR, "ldap: no basedn specified for realm '%s'", realm); + ldap_unbind_s(data->ld); + data->ld = NULL; + return NULL; + } + + if(ldap_simple_bind_s(data->ld, data->binddn, data->bindpw) + && (_ldap_connect(data) || ldap_simple_bind_s(data->ld, data->binddn, data->bindpw))) + { + log_write(data->ar->c2s->log, LOG_ERR, "ldap: bind failed: %s", ldap_err2string(_ldap_get_lderrno(data->ld))); + ldap_unbind_s(data->ld); + data->ld = NULL; + return NULL; + } + + snprintf(filter, 1024, "(%s=%s)", data->uidattr, username); + + if(ldap_search_s(data->ld, basedn, LDAP_SCOPE_SUBTREE, filter, no_attrs, 0, &result)) + { + log_write(data->ar->c2s->log, LOG_ERR, "ldap: search %s failed: %s", filter, ldap_err2string(_ldap_get_lderrno(data->ld))); + ldap_unbind_s(data->ld); + data->ld = NULL; + return NULL; + } + + entry = ldap_first_entry(data->ld, result); + if(entry == NULL) + { + ldap_msgfree(result); + + return NULL; + } + + dn = ldap_get_dn(data->ld, entry); + + ldap_msgfree(result); + + log_debug(ZONE, "got dn '%s' from realm '%s', user '%s'", dn, realm, username); + + return dn; +} + +/** do we have this user? */ +static int _ldap_user_exists(authreg_t ar, char *username, char *realm) +{ + char *dn; + int result; + + moddata_t data = (moddata_t) ar->private; + + if(data->ld == NULL && _ldap_connect(data)) + return 0; + + dn = _ldap_search(data, realm, username); + result = (int) dn; + ldap_memfree(dn); + + return result; +} + +/** check the password */ +static int _ldap_check_password(authreg_t ar, char *username, char *realm, char password[257]) +{ + moddata_t data = (moddata_t) ar->private; + char *dn; + + if(password[0] == '\0') + return 1; + + if(data->ld == NULL && _ldap_connect(data)) + return 1; + + dn = _ldap_search(data, realm, username); + if(dn == NULL) + return 1; + + if(ldap_simple_bind_s(data->ld, dn, password)) + { + if(_ldap_get_lderrno(data->ld) != LDAP_INVALID_CREDENTIALS) + { + log_write(data->ar->c2s->log, LOG_ERR, "ldap: bind as '%s' failed: %s", dn, ldap_err2string(_ldap_get_lderrno(data->ld))); + ldap_unbind_s(data->ld); + data->ld = NULL; + } + + ldap_memfree(dn); + return 1; + } + + ldap_memfree(dn); + return 0; +} + +/** shut me down */ +static void _ldap_free(authreg_t ar) +{ + moddata_t data = (moddata_t) ar->private; + + if(data->ld != NULL) + ldap_unbind_s(data->ld); + + xhash_free(data->basedn); + free(data); + + return; +} + +/** start me up */ +int ar_ldap_init(authreg_t ar) +{ + moddata_t data; + char *host, *realm; + config_elem_t basedn; + int i; + + host = config_get_one(ar->c2s->config, "authreg.ldap.host", 0); + if(host == NULL) + { + log_write(ar->c2s->log, LOG_ERR, "ldap: no host specified in config file"); + return 1; + } + + basedn = config_get(ar->c2s->config, "authreg.ldap.basedn"); + if(basedn == NULL) + { + log_write(ar->c2s->log, LOG_ERR, "ldap: no basedns specified in config file"); + return 1; + } + + data = (moddata_t) malloc(sizeof(struct moddata_st)); + memset(data, 0, sizeof(struct moddata_st)); + + data->basedn = xhash_new(101); + + for(i = 0; i < basedn->nvalues; i++) + { + realm = (basedn->attrs[i] != NULL) ? j_attr((const char **) basedn->attrs[i], "realm") : NULL; + if(realm == NULL) + data->default_basedn = basedn->values[i]; + else + xhash_put(data->basedn, realm, basedn->values[i]); + + log_debug(ZONE, "realm '%s' has base dn '%s'", realm, basedn->values[i]); + } + + log_write(ar->c2s->log, LOG_NOTICE, "ldap: configured %d realms", i); + + data->host = host; + + data->port = j_atoi(config_get_one(ar->c2s->config, "authreg.ldap.port", 0), 389); + + data->flags = AR_LDAP_FLAGS_NONE; + + if(config_get(ar->c2s->config, "authreg.ldap.v3") != NULL) + data->flags |= AR_LDAP_FLAGS_V3; + if(config_get(ar->c2s->config, "authreg.ldap.starttls") != NULL) + data->flags |= AR_LDAP_FLAGS_STARTTLS; + if(config_get(ar->c2s->config, "authreg.ldap.ssl") != NULL) + data->flags |= AR_LDAP_FLAGS_SSL; + + if((data->flags & AR_LDAP_FLAGS_STARTTLS) && (data->flags & AR_LDAP_FLAGS_SSL)) { + log_write(ar->c2s->log, LOG_ERR, "ldap: not possible to use both SSL and starttls"); + return 1; + } + + data->binddn = config_get_one(ar->c2s->config, "authreg.ldap.binddn", 0); + if(data->binddn != NULL) + data->bindpw = config_get_one(ar->c2s->config, "authreg.ldap.bindpw", 0); + + data->uidattr = config_get_one(ar->c2s->config, "authreg.ldap.uidattr", 0); + if(data->uidattr == NULL) + data->uidattr = "uid"; + + data->ar = ar; + + if(_ldap_connect(data)) + { + xhash_free(data->basedn); + free(data); + return 1; + } + + ar->private = data; + + ar->user_exists = _ldap_user_exists; + ar->check_password = _ldap_check_password; + ar->free = _ldap_free; + + return 0; +} + +#endif diff --git a/c2s/authreg_mysql.c b/c2s/authreg_mysql.c new file mode 100644 index 00000000..7ca6ea35 --- /dev/null +++ b/c2s/authreg_mysql.c @@ -0,0 +1,543 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* this module talks to a MySQL server via libmysqlclient */ + +#include "c2s.h" + +#ifdef STORAGE_MYSQL + +#define MYSQL_LU 1024 /* maximum length of username - should correspond to field length */ +#define MYSQL_LR 256 /* maximum length of realm - should correspond to field length */ +#define MYSQL_LP 256 /* maximum length of password - should correspond to field length */ + +#include + +typedef struct mysqlcontext_st { + MYSQL * conn; + char * sql_create; + char * sql_select; + char * sql_setpassword; + char * sql_setzerok; + char * sql_delete; + char * field_password; + char * field_hash; + char * field_token; + char * field_sequence; + } *mysqlcontext_t; + +static MYSQL_RES *_ar_mysql_get_user_tuple(authreg_t ar, char *username, char *realm) { + mysqlcontext_t ctx = (mysqlcontext_t) ar->private; + MYSQL *conn = ctx->conn; + char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1]; + char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], sql[1024 + MYSQL_LU*2 + MYSQL_LR*2 + 1]; /* query(1024) + euser + erealm + \0(1) */ + MYSQL_RES *res; + + if(mysql_ping(conn) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost"); + return NULL; + } + + snprintf(iuser, MYSQL_LU+1, "%s", username); + snprintf(irealm, MYSQL_LR+1, "%s", realm); + + mysql_real_escape_string(conn, euser, iuser, strlen(iuser)); + mysql_real_escape_string(conn, erealm, irealm, strlen(irealm)); + + sprintf(sql, ctx->sql_select, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + if(mysql_query(conn, sql) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: sql select failed: %s", mysql_error(conn)); + return NULL; + } + + res = mysql_store_result(conn); + if(res == NULL) { + log_write(ar->c2s->log, LOG_ERR, "mysql: sql result retrieval failed: %s", mysql_error(conn)); + return NULL; + } + + if(mysql_num_rows(res) != 1) { + mysql_free_result(res); + return NULL; + } + + return res; +} + +static int _ar_mysql_user_exists(authreg_t ar, char *username, char *realm) { + MYSQL_RES *res = _ar_mysql_get_user_tuple(ar, username, realm); + + if(res != NULL) { + mysql_free_result(res); + return 1; + } + + return 0; +} + +static int _ar_mysql_get_password(authreg_t ar, char *username, char *realm, char password[257]) { + mysqlcontext_t ctx = (mysqlcontext_t) ar->private; + MYSQL *conn = ctx->conn; + MYSQL_RES *res = _ar_mysql_get_user_tuple(ar, username, realm); + MYSQL_FIELD *field; + MYSQL_ROW tuple; + int i, fpass = 0; + + if(res == NULL) + return 1; + + for(i = mysql_num_fields(res) - 1; i >= 0; i--) { + field = mysql_fetch_field_direct(res, i); + if(strcmp(field->name, ctx->field_password) == 0) { + fpass = i; + break; + } + } + + if((tuple = mysql_fetch_row(res)) == NULL) { + log_write(ar->c2s->log, LOG_ERR, "mysql: sql tuple retrieval failed: %s", mysql_error(conn)); + mysql_free_result(res); + return 1; + } + + if(tuple[fpass] == NULL) { + mysql_free_result(res); + return 1; + } + + strcpy(password, tuple[fpass]); + + mysql_free_result(res); + + return 0; +} + +static int _ar_mysql_set_password(authreg_t ar, char *username, char *realm, char password[257]) { + mysqlcontext_t ctx = (mysqlcontext_t) ar->private; + MYSQL *conn = ctx->conn; + char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1]; + char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], epass[513], sql[1024+MYSQL_LU*2+MYSQL_LR*2+512+1]; /* query(1024) + euser + erealm + epass(512) + \0(1) */ + + if(mysql_ping(conn) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost"); + return 1; + } + + snprintf(iuser, MYSQL_LU+1, "%s", username); + snprintf(irealm, MYSQL_LR+1, "%s", realm); + + password[256]= '\0'; + + mysql_real_escape_string(conn, euser, iuser, strlen(iuser)); + mysql_real_escape_string(conn, erealm, irealm, strlen(irealm)); + mysql_real_escape_string(conn, epass, password, strlen(password)); + + sprintf(sql, ctx->sql_setpassword, epass, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + if(mysql_query(conn, sql) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: sql update failed: %s", mysql_error(conn)); + return 1; + } + + return 0; +} + +static int _ar_mysql_get_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int *sequence) { + mysqlcontext_t ctx = (mysqlcontext_t) ar->private; + MYSQL *conn = ctx->conn; + MYSQL_RES *res = _ar_mysql_get_user_tuple(ar, username, realm); + int i, fhash, ftok, fseq; + MYSQL_FIELD *field; + MYSQL_ROW tuple; + + if(res == NULL) + return 1; + + fhash = ftok = fseq = 0; + for(i = mysql_num_fields(res) - 1; i >= 0; i--) { + field = mysql_fetch_field_direct(res, i); + if(strcmp(field->name, ctx->field_hash) == 0) + fhash = i; + else if(strcmp(field->name, ctx->field_token) == 0) + ftok = i; + else if(strcmp(field->name, ctx->field_sequence) == 0) + fseq = i; + } + + if((tuple = mysql_fetch_row(res)) == NULL) { + log_write(ar->c2s->log, LOG_ERR, "mysql: sql tuple retrieval failed: %s", mysql_error(conn)); + mysql_free_result(res); + return 1; + } + + if(tuple[fhash] == NULL || tuple[ftok] == NULL || tuple[fseq] == NULL) { + mysql_free_result(res); + return 1; + } + + strcpy(hash, tuple[fhash]); + strcpy(token, tuple[ftok]); + *sequence = atoi(tuple[fseq]); + + mysql_free_result(res); + + return 0; +} + +static int _ar_mysql_set_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int sequence) { + mysqlcontext_t ctx = (mysqlcontext_t) ar->private; + MYSQL *conn = ctx->conn; + char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1]; + char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], ehash[81], etoken[21], sql[1024+MYSQL_LU*2+MYSQL_LR*2+80+20+12+1]; /* query(1024) + euser + erealm + ehash(80) + etoken(20) + sequence(12) + \0(1) */ + + if(mysql_ping(conn) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost"); + return 1; + } + + snprintf(iuser, MYSQL_LU+1, "%s", username); + snprintf(irealm, MYSQL_LR+1, "%s", realm); + + mysql_real_escape_string(conn, euser, iuser, strlen(iuser)); + mysql_real_escape_string(conn, erealm, irealm, strlen(irealm)); + mysql_real_escape_string(conn, ehash, hash, strlen(hash)); + mysql_real_escape_string(conn, etoken, token, strlen(token)); + + sprintf(sql, ctx->sql_setzerok, ehash, etoken, sequence, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + if(mysql_query(conn, sql) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: sql update failed: %s", mysql_error(conn)); + return 1; + } + + return 0; +} + +static int _ar_mysql_create_user(authreg_t ar, char *username, char *realm) { + mysqlcontext_t ctx = (mysqlcontext_t) ar->private; + MYSQL *conn = ctx->conn; + char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1]; + char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], sql[1024+MYSQL_LU*2+MYSQL_LR*2+1]; /* query(1024) + euser + erealm + \0(1) */ + MYSQL_RES *res = _ar_mysql_get_user_tuple(ar, username, realm); + + if(res != NULL) { + mysql_free_result(res); + return 1; + } + + mysql_free_result(res); + + if(mysql_ping(conn) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost"); + return 1; + } + + snprintf(iuser, MYSQL_LU+1, "%s", username); + snprintf(irealm, MYSQL_LR+1, "%s", realm); + + mysql_real_escape_string(conn, euser, iuser, strlen(iuser)); + mysql_real_escape_string(conn, erealm, irealm, strlen(irealm)); + + sprintf(sql, ctx->sql_create, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + if(mysql_query(conn, sql) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: sql insert failed: %s", mysql_error(conn)); + return 1; + } + + return 0; +} + +static int _ar_mysql_delete_user(authreg_t ar, char *username, char *realm) { + mysqlcontext_t ctx = (mysqlcontext_t) ar->private; + MYSQL *conn = ctx->conn; + char iuser[MYSQL_LU+1], irealm[MYSQL_LR+1]; + char euser[MYSQL_LU*2+1], erealm[MYSQL_LR*2+1], sql[1024+MYSQL_LU*2+MYSQL_LR*2+1]; /* query(1024) + euser + erealm + \0(1) */ + + if(mysql_ping(conn) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database lost"); + return 1; + } + + snprintf(iuser, MYSQL_LU+1, "%s", username); + snprintf(irealm, MYSQL_LR+1, "%s", realm); + + mysql_real_escape_string(conn, euser, iuser, strlen(iuser)); + mysql_real_escape_string(conn, erealm, irealm, strlen(irealm)); + + sprintf(sql, ctx->sql_delete, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + if(mysql_query(conn, sql) != 0) { + log_write(ar->c2s->log, LOG_ERR, "mysql: sql insert failed: %s", mysql_error(conn)); + return 1; + } + + return 0; +} + +static void _ar_mysql_free(authreg_t ar) { + mysqlcontext_t ctx = (mysqlcontext_t) ar->private; + MYSQL *conn = ctx->conn; + + if(conn != NULL) + mysql_close(conn); + + free(ctx->sql_create); + free(ctx->sql_select); + free(ctx->sql_setpassword); + free(ctx->sql_setzerok); + free(ctx->sql_delete); + free(ctx); +} + +/** Provide a configuration parameter or default value. */ +char * _ar_mysql_param( config_t c, char * key, char * def ) { + char * value = config_get_one( c, key, 0 ); + if( value == NULL ) + return def; + else + return value; +} + +/* Ensure the sprintf template is less than 1K long and contains the */ +/* required parameter placeholder types. The types string contains */ +/* one each, in order, of the one character sprintf types that are */ +/* expected to follow the escape characters '%' in the template. */ +/* Returns 0 on success, or an error message on failures. */ +char * _ar_mysql_check_template( char * template, char * types ) { + int pScan = 0; + int pType = 0; + char c; + + /* check that it's 1K or less */ + if( strlen( template ) > 1024 ) return "longer than 1024 characters"; + + /* count the parameter placeholders */ + while( pScan < strlen( template ) ) + { + if( template[ pScan++ ] != '%' ) continue; + + c = template[ pScan++ ]; + if( c == '%' ) continue; /* ignore escaped precentages */ + if( c == types[ pType ] ) + { + /* we found the placeholder */ + pType++; /* search for the next type */ + continue; + } + + /* we found an unexpected placeholder type */ + return "contained unexpected placeholder type"; + } + + if( pType < strlen( types ) ) + return "contained too few placeholders"; + else + return 0; +} + +/* Ensure the SQL template is less than 1K long and contains the */ +/* required parameter placeholders. If there is an error, it is */ +/* written to the error log. */ +/* Returns 0 on success, or 1 on errors. */ +int _ar_mysql_check_sql( authreg_t ar, char * sql, char * types ) { + char * error; + + error = _ar_mysql_check_template( sql, types ); + if( error == 0 ) return 0; /* alls right :) */ + + /* signal error */ + log_write( ar->c2s->log, LOG_ERR, "mysql: template error: %s - %s", error, sql ); + return 1; +} + +/** start me up */ +int ar_mysql_init(authreg_t ar) { + char *host, *port, *dbname, *user, *pass; + char *create, *select, *setpassword, *setzerok, *delete; + char *table, *username, *realm; + char *template; + int strlentur; /* string length of table, user, and realm strings */ + MYSQL *conn; + mysqlcontext_t mysqlcontext; + + /* configure the database context with field names and SQL statements */ + mysqlcontext = (mysqlcontext_t) malloc( sizeof( struct mysqlcontext_st ) ); + ar->private = mysqlcontext; + ar->free = _ar_mysql_free; + + /* determine our field names and table name */ + username = _ar_mysql_param( ar->c2s->config + , "authreg.mysql.field.username" + , "username" ); + realm = _ar_mysql_param( ar->c2s->config + , "authreg.mysql.field.realm" + , "realm" ); + mysqlcontext->field_password = _ar_mysql_param( ar->c2s->config + , "authreg.mysql.field.password" + , "password" ); + mysqlcontext->field_hash = _ar_mysql_param( ar->c2s->config + , "authreg.mysql.field.hash" + , "hash" ); + mysqlcontext->field_token = _ar_mysql_param( ar->c2s->config + , "authreg.mysql.field.token" + , "token" ); + mysqlcontext->field_sequence = _ar_mysql_param( ar->c2s->config + , "authreg.mysql.field.sequence" + , "sequence" ); + table = _ar_mysql_param( ar->c2s->config + , "authreg.mysql.table" + , "authreg" ); + + /* craft the default SQL statements */ + /* we leave unused statements allocated to simplify code - a small price to pay */ + /* bounds checking and parameter format verification will be perfomed if the statement is used (see next section) */ + /* For malloc(), there is no +1 for trailing 0 as parameter substitution will net us several extra characters */ + + strlentur = strlen( table ) + strlen( username) + strlen( realm ); /* avoid repetition */ + + template = "INSERT INTO `%s` ( `%s`, `%s` ) VALUES ( '%%s', '%%s' )"; + create = malloc( strlen( template ) + strlentur ); + sprintf( create, template, table, username, realm ); + + template = "SELECT `%s`,`%s`,`%s`,`%s` FROM `%s` WHERE `%s` = '%%s' AND `%s` = '%%s'"; + select = malloc( strlen( template ) + + strlen( mysqlcontext->field_password ) + + strlen( mysqlcontext->field_hash ) + strlen( mysqlcontext->field_token ) + + strlen( mysqlcontext->field_sequence ) + + strlentur ); + sprintf( select, template + , mysqlcontext->field_password + , mysqlcontext->field_hash, mysqlcontext->field_token, mysqlcontext->field_sequence + , table, username, realm ); + + template = "UPDATE `%s` SET `%s` = '%%s' WHERE `%s` = '%%s' AND `%s` = '%%s'"; + setpassword = malloc( strlen( template ) + strlentur + strlen( mysqlcontext->field_password ) ); + sprintf( setpassword, template, table, mysqlcontext->field_password, username, realm ); + + template = "UPDATE `%s` SET `%s` = '%%s', `%s` = '%%s', `%s` = '%%d' WHERE `%s` = '%%s' AND `%s` = '%%s'"; + setzerok = malloc( strlen( template ) + strlentur + + strlen( mysqlcontext->field_hash ) + strlen( mysqlcontext->field_token ) + + strlen( mysqlcontext->field_sequence ) ); + sprintf( setzerok, template, table + , mysqlcontext->field_hash, mysqlcontext->field_token, mysqlcontext->field_sequence + , username, realm ); + + template = "DELETE FROM `%s` WHERE `%s` = '%%s' AND `%s` = '%%s'"; + delete = malloc( strlen( template ) + strlentur ); + sprintf( delete, template, table, username, realm ); + + /* allow the default SQL statements to be overridden; also verify the statements format and length */ + mysqlcontext->sql_create = strdup(_ar_mysql_param( ar->c2s->config + , "authreg.mysql.sql.create" + , create )); + if( _ar_mysql_check_sql( ar, mysqlcontext->sql_create, "ss" ) != 0 ) return 1; + + mysqlcontext->sql_select = strdup(_ar_mysql_param( ar->c2s->config + , "authreg.mysql.sql.select" + , select )); + if( _ar_mysql_check_sql( ar, mysqlcontext->sql_select, "ss" ) != 0 ) return 1; + + mysqlcontext->sql_setpassword = strdup(_ar_mysql_param( ar->c2s->config + , "authreg.mysql.sql.setpassword" + , setpassword )); + if( _ar_mysql_check_sql( ar, mysqlcontext->sql_setpassword, "sss" ) != 0 ) return 1; + + mysqlcontext->sql_setzerok = strdup(_ar_mysql_param( ar->c2s->config + , "authreg.mysql.sql.setzerok" + , setzerok )); + if( _ar_mysql_check_sql( ar, mysqlcontext->sql_setzerok, "ssdss" ) != 0 ) return 1; + + mysqlcontext->sql_delete = strdup(_ar_mysql_param( ar->c2s->config + , "authreg.mysql.sql.delete" + , delete )); + if( _ar_mysql_check_sql( ar, mysqlcontext->sql_delete, "ss" ) != 0 ) return 1; + + /* echo our configuration to debug */ + log_debug( ZONE, "SQL to create account: %s", mysqlcontext->sql_create ); + log_debug( ZONE, "SQL to query user information: %s", mysqlcontext->sql_select ); + log_debug( ZONE, "SQL to set password: %s", mysqlcontext->sql_setpassword ); + log_debug( ZONE, "SQL to set zero K: %s", mysqlcontext->sql_setzerok ); + log_debug( ZONE, "SQL to delete account: %s", mysqlcontext->sql_delete ); + + free(create); + free(select); + free(setpassword); + free(setzerok); + free(delete); + + host = config_get_one(ar->c2s->config, "authreg.mysql.host", 0); + port = config_get_one(ar->c2s->config, "authreg.mysql.port", 0); + dbname = config_get_one(ar->c2s->config, "authreg.mysql.dbname", 0); + user = config_get_one(ar->c2s->config, "authreg.mysql.user", 0); + pass = config_get_one(ar->c2s->config, "authreg.mysql.pass", 0); + + if(host == NULL || port == NULL || dbname == NULL || user == NULL || pass == NULL) { + log_write(ar->c2s->log, LOG_ERR, "mysql: invalid module config"); + return 1; + } + + log_debug( ZONE, "mysql connecting as '%s' to database '%s' on %s:%s", user, dbname, host, port ); + + conn = mysql_init(NULL); + mysqlcontext->conn = conn; + + if(conn == NULL) { + log_write(ar->c2s->log, LOG_ERR, "mysql: unable to allocate database connection state"); + return 1; + } + +#if MYSQL_VERSION_ID > 32349 + /* Set mysql_options for client ver 3.23.50 or later (workaround for mysql bug in 3.23.49) */ + mysql_options(conn, MYSQL_READ_DEFAULT_GROUP, "jabberd"); +#endif + + /* connect with CLIENT_INTERACTIVE to get a (possibly) higher timeout value than default */ + if(mysql_real_connect(conn, host, user, pass, dbname, atoi(port), NULL, CLIENT_INTERACTIVE) == NULL) { + log_write(ar->c2s->log, LOG_ERR, "mysql: connection to database failed: %s", mysql_error(conn)); + return 1; + } + + /* Set reconnect flag to 1 (set to 0 by default from mysql 5 on) */ + conn->reconnect = 1; + + ar->user_exists = _ar_mysql_user_exists; + ar->get_password = _ar_mysql_get_password; + ar->set_password = _ar_mysql_set_password; + ar->get_zerok = _ar_mysql_get_zerok; + ar->set_zerok = _ar_mysql_set_zerok; + ar->create_user = _ar_mysql_create_user; + ar->delete_user = _ar_mysql_delete_user; + + return 0; +} + +#endif diff --git a/c2s/authreg_pam.c b/c2s/authreg_pam.c new file mode 100644 index 00000000..741f8970 --- /dev/null +++ b/c2s/authreg_pam.c @@ -0,0 +1,117 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* this plugin uses PAM for authentication */ + +#include "c2s.h" + +#ifdef STORAGE_PAM + +#include + +static int _ar_pam_user_exists(authreg_t ar, char *username, char *realm) { + /* we can't check if a user exists, so we just assume we have them all the time */ + return 1; +} + +static int _ar_pam_conversation(int nmsg, const struct pam_message **msg, struct pam_response **res, void *arg) { + int i; + struct pam_response *reply; + + if(nmsg <= 0) + return PAM_CONV_ERR; + + reply = (struct pam_response *) malloc(sizeof(struct pam_response) * nmsg); + memset(reply, 0, sizeof(struct pam_response) * nmsg); + + for(i = 0; i < nmsg; i++) { + if(msg[i]->msg_style == PAM_PROMPT_ECHO_OFF || msg[i]->msg_style == PAM_PROMPT_ECHO_ON) { + reply[i].resp = strdup((char *) arg); + reply[i].resp_retcode = 0; + } + } + + *res = reply; + + return PAM_SUCCESS; +} + +#ifdef PAM_FAIL_DELAY +static int _ar_pam_delay(int ret, unsigned int usec, void *arg) { + /* !!! hack the current byterate limit to throttle the connection */ + return PAM_SUCCESS; +} +#endif + +static int _ar_pam_check_password(authreg_t ar, char *username, char *realm, char password[257]) { + struct pam_conv conv; + pam_handle_t *pam; + int ret; + + conv.conv = _ar_pam_conversation; + conv.appdata_ptr = password; + + ret = pam_start("jabberd", username, &conv, &pam); + if(ret != PAM_SUCCESS) { + log_write(ar->c2s->log, LOG_ERR, "pam: couldn't initialise PAM: %s", pam_strerror(NULL, ret)); + return 1; + } + +#ifdef PAM_FAIL_DELAY + ret = pam_set_item(pam, PAM_FAIL_DELAY, _ar_pam_delay); + if(ret != PAM_SUCCESS) { + log_write(ar->c2s->log, LOG_ERR, "pam: couldn't disable fail delay: %s", pam_strerror(NULL, ret)); + return 1; + } +#endif + + ret = pam_authenticate(pam, 0); + if(ret == PAM_AUTHINFO_UNAVAIL || ret == PAM_USER_UNKNOWN) { + pam_end(pam, ret); + return 1; + } + + if(ret != PAM_SUCCESS) { + log_write(ar->c2s->log, LOG_ERR, "pam: couldn't authenticate: %s", pam_strerror(NULL, ret)); + pam_end(pam, ret); + return 1; + } + + ret = pam_acct_mgmt(pam, 0); + if(ret != PAM_SUCCESS) { + log_write(ar->c2s->log, LOG_ERR, "pam: auth succeeded, but can't use account: %s", pam_strerror(NULL, ret)); + pam_end(pam, ret); + return 1; + } + + pam_end(pam, ret); + + return 0; +} + +/** start me up */ +int ar_pam_init(authreg_t ar) { + ar->user_exists = _ar_pam_user_exists; + ar->check_password = _ar_pam_check_password; + + return 0; +} + +#endif diff --git a/c2s/authreg_pgsql.c b/c2s/authreg_pgsql.c new file mode 100644 index 00000000..a4456f24 --- /dev/null +++ b/c2s/authreg_pgsql.c @@ -0,0 +1,532 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* this module talks to a PostgreSQL server via libpq */ + +#include "c2s.h" + +#ifdef STORAGE_PGSQL + +#include + +#define PGSQL_LU 1024 /* maximum length of username - should correspond to field length */ +#define PGSQL_LR 256 /* maximum length of realm - should correspond to field length */ +#define PGSQL_LP 256 /* maximum length of password - should correspond to field length */ + +typedef struct pgsqlcontext_st { + PGconn * conn; + char * sql_create; + char * sql_select; + char * sql_setpassword; + char * sql_setzerok; + char * sql_delete; + char * field_password; + char * field_hash; + char * field_token; + char * field_sequence; + } *pgsqlcontext_t; + +static PGresult *_ar_pgsql_get_user_tuple(authreg_t ar, char *username, char *realm) { + pgsqlcontext_t ctx = (pgsqlcontext_t) ar->private; + PGconn *conn = ctx->conn; + + char iuser[PGSQL_LU+1], irealm[PGSQL_LR+1]; + char euser[PGSQL_LU*2+1], erealm[PGSQL_LR*2+1], sql[1024+PGSQL_LU*2+PGSQL_LR*2+1]; /* query(1024) + euser + erealm + \0(1) */ + PGresult *res; + + snprintf(iuser, PGSQL_LU+1, "%s", username); + snprintf(irealm, PGSQL_LR+1, "%s", realm); + + PQescapeString(euser, iuser, strlen(iuser)); + PQescapeString(erealm, irealm, strlen(irealm)); + + sprintf(sql, ctx->sql_select, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + res = PQexec(conn, sql); + if(PQresultStatus(res) != PGRES_TUPLES_OK && PQstatus(conn) != CONNECTION_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(conn); + res = PQexec(conn, sql); + } + if(PQresultStatus(res) != PGRES_TUPLES_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: sql select failed: %s", PQresultErrorMessage(res)); + PQclear(res); + return NULL; + } + + if(PQntuples(res) != 1) { + PQclear(res); + return NULL; + } + + return res; +} + +static int _ar_pgsql_user_exists(authreg_t ar, char *username, char *realm) { + PGresult *res = _ar_pgsql_get_user_tuple(ar, username, realm); + + if(res != NULL) { + PQclear(res); + return 1; + } + + return 0; +} + +static int _ar_pgsql_get_password(authreg_t ar, char *username, char *realm, char password[257]) { + pgsqlcontext_t ctx = (pgsqlcontext_t) ar->private; + PGresult *res = _ar_pgsql_get_user_tuple(ar, username, realm); + int fpass; + + if(res == NULL) + return 1; + + fpass = PQfnumber(res, ctx->field_password); + if(fpass == -1) { + log_debug(ZONE, "weird, password field wasn't returned"); + PQclear(res); + return 1; + } + + if(PQgetisnull(res, 0, fpass)) { + PQclear(res); + return 1; + } + + strcpy(password, PQgetvalue(res, 0, fpass)); + + PQclear(res); + + return 0; +} + +static int _ar_pgsql_set_password(authreg_t ar, char *username, char *realm, char password[257]) { + pgsqlcontext_t ctx = (pgsqlcontext_t) ar->private; + PGconn *conn = ctx->conn; + char iuser[PGSQL_LU+1], irealm[PGSQL_LR+1]; + char euser[PGSQL_LU*2+1], erealm[PGSQL_LR*2+1], epass[513], sql[1024+PGSQL_LU*2+PGSQL_LR*2+512+1]; /* query(1024) + euser + erealm + epass(512) + \0(1) */ + PGresult *res; + + snprintf(iuser, PGSQL_LU+1, "%s", username); + snprintf(irealm, PGSQL_LR+1, "%s", realm); + + PQescapeString(euser, iuser, strlen(iuser)); + PQescapeString(erealm, irealm, strlen(irealm)); + PQescapeString(epass, password, strlen(password)); + + sprintf(sql, ctx->sql_setpassword, epass, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + res = PQexec(conn, sql); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(conn) != CONNECTION_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(conn); + res = PQexec(conn, sql); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: sql update failed: %s", PQresultErrorMessage(res)); + PQclear(res); + return 1; + } + + PQclear(res); + + return 0; +} + +static int _ar_pgsql_get_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int *sequence) { + pgsqlcontext_t ctx = (pgsqlcontext_t) ar->private; + PGresult *res = _ar_pgsql_get_user_tuple(ar, username, realm); + int fhash, ftok, fseq; + + if(res == NULL) + return 1; + + fhash = PQfnumber(res, ctx->field_hash); + ftok = PQfnumber(res, ctx->field_token); + fseq = PQfnumber(res, ctx->field_sequence); + if(fhash == -1 || ftok == -1 || fseq == -1) { + log_debug(ZONE, "weird, required field wasn't returned"); + PQclear(res); + return 1; + } + + if(PQgetisnull(res, 0, fhash) || PQgetisnull(res, 0, ftok) || PQgetisnull(res, 0, fseq)) { + PQclear(res); + return 1; + } + + strcpy(hash, PQgetvalue(res, 0, fhash)); + strcpy(token, PQgetvalue(res, 0, ftok)); + *sequence = atoi(PQgetvalue(res, 0, fseq)); + + PQclear(res); + + return 0; +} + +static int _ar_pgsql_set_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int sequence) { + pgsqlcontext_t ctx = (pgsqlcontext_t) ar->private; + PGconn *conn = ctx->conn; + char iuser[PGSQL_LU+1], irealm[PGSQL_LR+1]; + char euser[PGSQL_LU*2+1], erealm[PGSQL_LR*2+1], ehash[81], etoken[21], sql[1024 + PGSQL_LU*2 + PGSQL_LR*2 + 80 + 20 + 12 + 1]; /* query(1024) + euser + erealm + ehash(80) + etoken(20) + sequence(12) + \0(1) */ + PGresult *res; + + snprintf(iuser, PGSQL_LU+1, "%s", username); + snprintf(irealm, PGSQL_LR+1, "%s", realm); + + PQescapeString(euser, iuser, strlen(iuser)); + PQescapeString(erealm, irealm, strlen(irealm)); + PQescapeString(ehash, hash, strlen(hash)); + PQescapeString(etoken, token, strlen(token)); + + sprintf(sql, ctx->sql_setzerok, ehash, etoken, sequence, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + res = PQexec(conn, sql); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(conn) != CONNECTION_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(conn); + res = PQexec(conn, sql); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: sql update failed: %s", PQresultErrorMessage(res)); + PQclear(res); + return 1; + } + + PQclear(res); + + return 0; +} + +static int _ar_pgsql_create_user(authreg_t ar, char *username, char *realm) { + pgsqlcontext_t ctx = (pgsqlcontext_t) ar->private; + PGconn *conn = ctx->conn; + char iuser[PGSQL_LU+1], irealm[PGSQL_LR+1]; + char euser[PGSQL_LU*2+1], erealm[PGSQL_LR*2+1], sql[1024+PGSQL_LU*2+PGSQL_LR*2+1]; /* query(1024) + euser + erealm + \0(1) */ + PGresult *res; + + res = _ar_pgsql_get_user_tuple(ar, username, realm); + if(res != NULL) { + PQclear(res); + return 1; + } + + PQclear(res); + + snprintf(iuser, PGSQL_LU+1, "%s", username); + snprintf(irealm, PGSQL_LR+1, "%s", realm); + + PQescapeString(euser, iuser, strlen(iuser)); + PQescapeString(erealm, irealm, strlen(irealm)); + + sprintf(sql, ctx->sql_create, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + res = PQexec(conn, sql); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(conn) != CONNECTION_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(conn); + res = PQexec(conn, sql); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: sql insert failed: %s", PQresultErrorMessage(res)); + PQclear(res); + return 1; + } + + PQclear(res); + + return 0; +} + +static int _ar_pgsql_delete_user(authreg_t ar, char *username, char *realm) { + pgsqlcontext_t ctx = (pgsqlcontext_t) ar->private; + PGconn *conn = ctx->conn; + char iuser[PGSQL_LU+1], irealm[PGSQL_LR+1]; + char euser[PGSQL_LU*2+1], erealm[PGSQL_LR*2+1], sql[1024+PGSQL_LU*2+PGSQL_LR*2+1]; /* query(1024) + euser + erealm + \0(1) */ + PGresult *res; + + snprintf(iuser, PGSQL_LU+1, "%s", username); + snprintf(irealm, PGSQL_LR+1, "%s", realm); + + PQescapeString(euser, iuser, strlen(iuser)); + PQescapeString(erealm, irealm, strlen(irealm)); + + sprintf(sql, ctx->sql_delete, euser, erealm); + + log_debug(ZONE, "prepared sql: %s", sql); + + res = PQexec(conn, sql); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(conn) != CONNECTION_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(conn); + res = PQexec(conn, sql); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: sql delete failed: %s", PQresultErrorMessage(res)); + PQclear(res); + return 1; + } + + PQclear(res); + + return 0; +} + +static void _ar_pgsql_free(authreg_t ar) { + pgsqlcontext_t ctx = (pgsqlcontext_t) ar->private; + PGconn *conn = ctx->conn; + + if(conn != NULL) + PQfinish(conn); + + free(ctx->sql_create); + free(ctx->sql_select); + free(ctx->sql_setpassword); + free(ctx->sql_setzerok); + free(ctx->sql_delete); + free(ctx); +} + +/** Provide a configuration parameter or default value. */ +char * _ar_pgsql_param( config_t c, char * key, char * def ) { + char * value = config_get_one( c, key, 0 ); + if( value == NULL ) + return def; + else + return value; +} + +/* Ensure the sprintf template is less than 1K long and contains the */ +/* required parameter placeholder types. The types string contains */ +/* one each, in order, of the one character sprintf types that are */ +/* expected to follow the escape characters '%' in the template. */ +/* Returns 0 on success, or an error message on failures. */ +char * _ar_pgsql_check_template( char * template, char * types ) { + int pScan = 0; + int pType = 0; + char c; + + /* check that it's 1K or less */ + if( strlen( template ) > 1024 ) return "longer than 1024 characters"; + + /* count the parameter placeholders */ + while( pScan < strlen( template ) ) + { + if( template[ pScan++ ] != '%' ) continue; + + c = template[ pScan++ ]; + if( c == '%' ) continue; /* ignore escaped precentages */ + if( c == types[ pType ] ) + { + /* we found the placeholder */ + pType++; /* search for the next type */ + continue; + } + + /* we found an unexpected placeholder type */ + return "contained unexpected placeholder type"; + } + + if( pType < strlen( types ) ) + return "contained too few placeholders"; + else + return 0; +} + +/* Ensure the SQL template is less than 1K long and contains the */ +/* required parameter placeholders. If there is an error, it is */ +/* written to the error log. */ +/* Returns 0 on success, or 1 on errors. */ +int _ar_pgsql_check_sql( authreg_t ar, char * sql, char * types ) { + char * error; + + error = _ar_pgsql_check_template( sql, types ); + if( error == 0 ) return 0; /* alls right :) */ + + /* signal error */ + log_write( ar->c2s->log, LOG_ERR, "pgsql: template error: %s - %s", error, sql ); + return 1; +} + +/** start me up */ +int ar_pgsql_init(authreg_t ar) { + char *host, *port, *dbname, *user, *pass; + char *create, *select, *setpassword, *setzerok, *delete; + char *table, *username, *realm; + char *template; + int strlentur; /* string length of table, user, and realm strings */ + PGconn *conn; + pgsqlcontext_t pgsqlcontext; + + /* configure the database context with field names and SQL statements */ + pgsqlcontext = (pgsqlcontext_t) malloc( sizeof( struct pgsqlcontext_st ) ); + memset(pgsqlcontext, 0, sizeof( struct pgsqlcontext_st )); + ar->private = pgsqlcontext; + ar->free = _ar_pgsql_free; + + /* determine our field names and table name */ + username = _ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.field.username" + , "username" ); + realm = _ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.field.realm" + , "realm" ); + pgsqlcontext->field_password = _ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.field.password" + , "password" ); + pgsqlcontext->field_hash = _ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.field.hash" + , "hash" ); + pgsqlcontext->field_token = _ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.field.token" + , "token" ); + pgsqlcontext->field_sequence = _ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.field.sequence" + , "sequence" ); + table = _ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.table" + , "authreg" ); + + /* craft the default SQL statements */ + /* we leave unused statements allocated to simplify code - a small price to pay */ + /* bounds checking and parameter format verification will be perfomed if the statement is used (see next section) */ + /* For malloc(), there is no +1 for trailing 0 as parameter substitution will net us several extra characters */ + + strlentur = strlen( table ) + strlen( username) + strlen( realm ); /* avoid repetition */ + + template = "INSERT INTO \"%s\" ( \"%s\", \"%s\" ) VALUES ( '%%s', '%%s' )"; + create = malloc( strlen( template ) + strlentur ); + sprintf( create, template, table, username, realm ); + + template = "SELECT \"%s\",\"%s\",\"%s\",\"%s\" FROM \"%s\" WHERE \"%s\" = '%%s' AND \"%s\" = '%%s'"; + select = malloc( strlen( template ) + + strlen( pgsqlcontext->field_password ) + + strlen( pgsqlcontext->field_hash ) + strlen( pgsqlcontext->field_token ) + + strlen( pgsqlcontext->field_sequence ) + + strlentur ); + sprintf( select, template + , pgsqlcontext->field_password + , pgsqlcontext->field_hash, pgsqlcontext->field_token, pgsqlcontext->field_sequence + , table, username, realm ); + + template = "UPDATE \"%s\" SET \"%s\" = '%%s' WHERE \"%s\" = '%%s' AND \"%s\" = '%%s'"; + setpassword = malloc( strlen( template ) + strlentur + strlen( pgsqlcontext->field_password ) ); + sprintf( setpassword, template, table, pgsqlcontext->field_password, username, realm ); + + template = "UPDATE \"%s\" SET \"%s\" = '%%s', \"%s\" = '%%s', \"%s\" = '%%d' WHERE \"%s\" = '%%s' AND \"%s\" = '%%s'"; + setzerok = malloc( strlen( template ) + strlentur + + strlen( pgsqlcontext->field_hash ) + strlen( pgsqlcontext->field_token ) + + strlen( pgsqlcontext->field_sequence ) ); + sprintf( setzerok, template, table + , pgsqlcontext->field_hash, pgsqlcontext->field_token, pgsqlcontext->field_sequence + , username, realm ); + + template = "DELETE FROM \"%s\" WHERE \"%s\" = '%%s' AND \"%s\" = '%%s'"; + delete = malloc( strlen( template ) + strlentur ); + sprintf( delete, template, table, username, realm ); + + /* allow the default SQL statements to be overridden; also verify the statements format and length */ + pgsqlcontext->sql_create = strdup(_ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.sql.create" + , create )); + if( _ar_pgsql_check_sql( ar, pgsqlcontext->sql_create, "ss" ) != 0 ) return 1; + + pgsqlcontext->sql_select = strdup(_ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.sql.select" + , select )); + if( _ar_pgsql_check_sql( ar, pgsqlcontext->sql_select, "ss" ) != 0 ) return 1; + + pgsqlcontext->sql_setpassword = strdup(_ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.sql.setpassword" + , setpassword )); + if( _ar_pgsql_check_sql( ar, pgsqlcontext->sql_setpassword, "sss" ) != 0 ) return 1; + + pgsqlcontext->sql_setzerok = strdup(_ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.sql.setzerok" + , setzerok )); + if( _ar_pgsql_check_sql( ar, pgsqlcontext->sql_setzerok, "ssdss" ) != 0 ) return 1; + + pgsqlcontext->sql_delete = strdup(_ar_pgsql_param( ar->c2s->config + , "authreg.pgsql.sql.delete" + , delete )); + if( _ar_pgsql_check_sql( ar, pgsqlcontext->sql_delete, "ss" ) != 0 ) return 1; + + /* echo our configuration to debug */ + log_debug( ZONE, "SQL to create account: %s", pgsqlcontext->sql_create ); + log_debug( ZONE, "SQL to query user information: %s", pgsqlcontext->sql_select ); + log_debug( ZONE, "SQL to set password: %s", pgsqlcontext->sql_setpassword ); + log_debug( ZONE, "SQL to set zero K: %s", pgsqlcontext->sql_setzerok ); + log_debug( ZONE, "SQL to delete account: %s", pgsqlcontext->sql_delete ); + + free(create); + free(select); + free(setpassword); + free(setzerok); + free(delete); + + host = config_get_one(ar->c2s->config, "authreg.pgsql.host", 0); + port = config_get_one(ar->c2s->config, "authreg.pgsql.port", 0); + dbname = config_get_one(ar->c2s->config, "authreg.pgsql.dbname", 0); + user = config_get_one(ar->c2s->config, "authreg.pgsql.user", 0); + pass = config_get_one(ar->c2s->config, "authreg.pgsql.pass", 0); + + if(host == NULL || port == NULL || dbname == NULL || user == NULL || pass == NULL) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: invalid module config"); + return 1; + } + + log_debug( ZONE, "pgsql connecting as '%s' to database '%s' on %s:%s", user, dbname, host, port ); + + conn = PQsetdbLogin(host, port, NULL, NULL, dbname, user, pass); + if(conn == NULL) { + log_write(ar->c2s->log, LOG_ERR, "pgsql: unable to allocate database connection state"); + return 1; + } + + if(PQstatus(conn) != CONNECTION_OK) + log_write(ar->c2s->log, LOG_ERR, "pgsql: connection to database failed, will retry later: %s", PQerrorMessage(conn)); + + pgsqlcontext->conn = conn; + + ar->user_exists = _ar_pgsql_user_exists; + ar->get_password = _ar_pgsql_get_password; + ar->set_password = _ar_pgsql_set_password; + ar->get_zerok = _ar_pgsql_get_zerok; + ar->set_zerok = _ar_pgsql_set_zerok; + ar->create_user = _ar_pgsql_create_user; + ar->delete_user = _ar_pgsql_delete_user; + + return 0; +} + +#endif diff --git a/c2s/authreg_pipe.c b/c2s/authreg_pipe.c new file mode 100644 index 00000000..3d105330 --- /dev/null +++ b/c2s/authreg_pipe.c @@ -0,0 +1,477 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* + * this is the fabled pipe authenticator. it forks and executes a + * script, and talks to it via stdio. this is a great way to take + * advantage of existing code (in any language) to do authentication and + * registration. + * + * there is an example script, tools/pipe-auth.pl, which can be used to + * get started writing a pipe module. the protocol is documented in + * docs/dev/c2s-pipe-authenticator + */ + +/* + * !!! this is highly experimental - be prepared for random acts of weirdness + * if you decide to use this. + */ + +#include "c2s.h" + +#ifdef STORAGE_PIPE + +#include + +/** internal structure, holds our data */ +typedef struct moddata_st { + char *exec; + + pid_t child; + + int in, out; +} *moddata_t; + +static int _ar_pipe_write(authreg_t ar, int fd, char *msgfmt, ...) +{ + va_list args; + char buf[1024]; + int ret; + + va_start(args, msgfmt); + vsnprintf(buf, 1024, msgfmt, args); + va_end(args); + + log_debug(ZONE, "writing to pipe: %s", buf); + + ret = write(fd, buf, strlen(buf)); + if(ret < 0) + log_write(ar->c2s->log, LOG_ERR, "pipe: write to pipe failed: %s", strerror(errno)); + + return ret; +} + +static int _ar_pipe_read(authreg_t ar, int fd, char *buf, int buflen) +{ + int ret; + char *c; + + ret = read(fd, buf, buflen); + if(ret == 0) + log_write(ar->c2s->log, LOG_ERR, "pipe: got EOF from pipe"); + if(ret < 0) + log_write(ar->c2s->log, LOG_ERR, "pipe: read from pipe failed: %s", strerror(errno)); + if(ret <= 0) + return ret; + + buf[ret] = '\0'; + c = strchr(buf, '\n'); + if(c != NULL) + *c = '\0'; + + log_debug(ZONE, "read from pipe: %s", buf); + + return ret; +} + +static int _ar_pipe_user_exists(authreg_t ar, char *username, char *realm) +{ + moddata_t data = (moddata_t) ar->private; + char buf[1024]; + + if(_ar_pipe_write(ar, data->out, "USER-EXISTS %s %s\n", username, realm) < 0) + return 0; + + if(_ar_pipe_read(ar, data->in, buf, 1023) <= 0) + return 0; + + if(buf[0] != 'O' || buf[1] != 'K') + return 0; + + return 1; +} + +static int _ar_pipe_get_password(authreg_t ar, char *username, char *realm, char password[257]) +{ + moddata_t data = (moddata_t) ar->private; + char buf[1024]; + + if(_ar_pipe_write(ar, data->out, "GET-PASSWORD %s %s\n", username, realm) < 0) + return 1; + + if(_ar_pipe_read(ar, data->in, buf, 1023) <= 0) + return 1; + + if(buf[0] != 'O' || buf[1] != 'K') + return 1; + + if(buf[2] != ' ' || buf[3] == '\0') + { + log_debug(ZONE, "malformed response from pipe"); + return 1; + } + + if(ap_base64decode_len(&buf[3], -1) >= 256) { + log_debug(ZONE, "decoded password longer than buffer"); + return 1; + } + + ap_base64decode(password, &buf[3], -1); + + log_debug(ZONE, "got password: %s", password); + + return 0; +} + +static int _ar_pipe_check_password(authreg_t ar, char *username, char *realm, char password[257]) +{ + moddata_t data = (moddata_t) ar->private; + char buf[1024]; + int plen; + + plen = strlen(password); + + if(ap_base64encode_len(plen) >= 1023) { + log_debug(ZONE, "unable to encode password"); + return 1; + } + + ap_base64encode(buf, password, plen); + + if(_ar_pipe_write(ar, data->out, "CHECK-PASSWORD %s %s %s\n", username, buf, realm) < 0) + return 1; + + if(_ar_pipe_read(ar, data->in, buf, 1023) <= 0) + return 1; + + if(buf[0] != 'O' || buf[1] != 'K') + return 1; + + return 0; +} + +static int _ar_pipe_set_password(authreg_t ar, char *username, char *realm, char password[257]) +{ + moddata_t data = (moddata_t) ar->private; + char buf[1024]; + int plen; + + plen = strlen(password); + + if(ap_base64encode_len(plen) >= 1023) { + log_debug(ZONE, "unable to encode password"); + return 1; + } + + ap_base64encode(buf, password, plen); + + if(_ar_pipe_write(ar, data->out, "SET-PASSWORD %s %s %s\n", username, buf, realm) < 0) + return 1; + + if(_ar_pipe_read(ar, data->in, buf, 1023) <= 0) + return 1; + + if(buf[0] != 'O' || buf[1] != 'K') + return 1; + + return 0; +} + +static int _ar_pipe_get_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int *sequence) +{ + moddata_t data = (moddata_t) ar->private; + char buf[1024], *tok, *c; + int i = 0; + + if(_ar_pipe_write(ar, data->out, "GET-ZEROK %s %s\n", username, realm) < 0) + return 1; + + if(_ar_pipe_read(ar, data->in, buf, 1023) <= 0) + return 1; + + if(buf[0] != 'O' || buf[1] != 'K') + return 1; + + if(buf[2] != ' ' || !((buf[3] >= '0' && buf[3] <= '9') || (buf[3] >= 'a' && buf[3] <= 'f'))) + { + log_debug(ZONE, "malformed response from pipe"); + return 1; + } + + c = &buf[3]; + while(c != NULL && i < 3) + { + tok = c; + + c = strchr(c, ' '); + if(c != NULL) + { + *c = '\0'; + c++; + } + + switch(i) + { + case 0: + snprintf(hash, 41, "%s", tok); + break; + case 1: + snprintf(token, 11, "%s", tok); + break; + case 2: + *sequence = atoi(tok); + break; + } + + i++; + } + + if(i < 3) + { + log_debug(ZONE, "malformed response from pipe"); + return 1; + } + + log_debug(ZONE, "got zerok: hash=%s, token=%s, sequence=%d", hash, token, sequence); + + return 0; +} + +static int _ar_pipe_set_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int sequence) +{ + moddata_t data = (moddata_t) ar->private; + char buf[1024]; + + if(_ar_pipe_write(ar, data->out, "SET-ZEROK %s %s %s %d %s\n", username, hash, token, sequence, realm) < 0) + return 1; + + if(_ar_pipe_read(ar, data->in, buf, 1023) <= 0) + return 1; + + if(buf[0] != 'O' || buf[1] != 'K') + return 1; + + return 0; +} + +static int _ar_pipe_create_user(authreg_t ar, char *username, char *realm) +{ + moddata_t data = (moddata_t) ar->private; + char buf[1024]; + + if(_ar_pipe_write(ar, data->out, "CREATE-USER %s %s\n", username, realm) < 0) + return 1; + + if(_ar_pipe_read(ar, data->in, buf, 1023) <= 0) + return 1; + + if(buf[0] != 'O' || buf[1] != 'K') + return 1; + + return 0; +} + +static int _ar_pipe_delete_user(authreg_t ar, char *username, char *realm) +{ + moddata_t data = (moddata_t) ar->private; + char buf[1024]; + + if(_ar_pipe_write(ar, data->out, "DELETE-USER %s %s\n", username, realm) < 0) + return 1; + + if(_ar_pipe_read(ar, data->in, buf, 1023) <= 0) + return 1; + + if(buf[0] != 'O' || buf[1] != 'K') + return 1; + + return 0; +} + +static void _ar_pipe_free(authreg_t ar) +{ + moddata_t data = (moddata_t) ar->private; + + if(_ar_pipe_write(ar, data->out, "FREE\n") < 0) + return; + + close(data->in); + close(data->out); + + free(data); + + return; +} + +static void _ar_pipe_signal(int signum) +{ + wait(NULL); + + /* !!! attempt to restart the pipe, or shutdown c2s */ +} + +/** start me up */ +int ar_pipe_init(authreg_t ar) +{ + moddata_t data; + int to[2], from[2], ret; + char buf[1024], *tok, *c; + + data = (moddata_t) malloc(sizeof(struct moddata_st)); + memset(data, 0, sizeof(struct moddata_st)); + + data->exec = config_get_one(ar->c2s->config, "authreg.pipe.exec", 0); + if(data->exec == NULL) + { + log_write(ar->c2s->log, LOG_ERR, "pipe: no executable specified in config file"); + free(data); + return 1; + } + + if(pipe(to) < 0) + { + log_write(ar->c2s->log, LOG_ERR, "pipe: failed to create pipe: %s", strerror(errno)); + free(data); + return 1; + } + + if(pipe(from) < 0) + { + log_write(ar->c2s->log, LOG_ERR, "pipe: failed to create pipe: %s", strerror(errno)); + close(to[0]); + close(to[1]); + free(data); + return 1; + } + + signal(SIGCHLD, _ar_pipe_signal); + + log_debug(ZONE, "attempting to fork"); + + data->child = fork(); + if(data->child < 0) + { + log_write(ar->c2s->log, LOG_ERR, "pipe: failed to fork: %s", strerror(errno)); + close(to[0]); + close(to[1]); + close(from[0]); + close(from[1]); + free(data); + return 1; + } + + /* child */ + if(data->child == 0) + { + log_debug(ZONE, "executing %s", data->exec); + + close(STDIN_FILENO); + close(STDOUT_FILENO); + + dup2(to[0], STDIN_FILENO); + dup2(from[1], STDOUT_FILENO); + + close(to[0]); + close(to[1]); + close(from[0]); + close(from[1]); + + execl(data->exec, data->exec, NULL); + + log_write(ar->c2s->log, LOG_ERR, "pipe: failed to execute %s: %s", data->exec, strerror(errno)); + + free(data); + + exit(1); + } + + log_write(ar->c2s->log, LOG_NOTICE, "pipe authenticator %s running (pid %d)", data->exec, data->child); + + /* parent */ + close(to[0]); + close(from[1]); + + data->in = from[0]; + data->out = to[1]; + + ret = _ar_pipe_read(ar, data->in, buf, 1023); + if(ret <= 0) + { + close(data->in); + close(data->out); + free(data); + return 1; + } + + c = buf; + while(c != NULL) + { + tok = c; + + c = strchr(c, ' '); + if(c != NULL) + { + *c = '\0'; + c++; + } + + /* first token must be OK */ + if(tok == buf) + { + if(strcmp(tok, "OK") == 0) + continue; + + log_write(ar->c2s->log, LOG_ERR, "pipe: pipe authenticator failed to initialise"); + kill(data->child, SIGTERM); + close(data->in); + close(data->out); + free(data); + return 1; + } + + /* its an option */ + log_debug(ZONE, "module feature: %s", tok); + + if(strcmp(tok, "USER-EXISTS") == 0) + ar->user_exists = _ar_pipe_user_exists; + else if(strcmp(tok, "GET-PASSWORD") == 0) + ar->get_password = _ar_pipe_get_password; + else if(strcmp(tok, "CHECK-PASSWORD") == 0) + ar->check_password = _ar_pipe_check_password; + else if(strcmp(tok, "SET-PASSWORD") == 0) + ar->set_password = _ar_pipe_set_password; + else if(strcmp(tok, "GET-ZEROK") == 0) + ar->get_zerok = _ar_pipe_get_zerok; + else if(strcmp(tok, "SET-ZEROK") == 0) + ar->set_zerok = _ar_pipe_set_zerok; + else if(strcmp(tok, "CREATE-USER") == 0) + ar->create_user = _ar_pipe_create_user; + else if(strcmp(tok, "DELETE-USER") == 0) + ar->delete_user = _ar_pipe_delete_user; + else if(strcmp(tok, "FREE") == 0) + ar->free = _ar_pipe_free; + } + + ar->private = (void *) data; + + return 0; +} + +#endif diff --git a/c2s/bind.c b/c2s/bind.c new file mode 100644 index 00000000..a870190d --- /dev/null +++ b/c2s/bind.c @@ -0,0 +1,56 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file c2s/bind.c + * @brief xmpp resource binding + * @author Robert Norris + * $Date: 2005/06/02 04:48:24 $ + * $Revision: 1.9 $ + */ + +#include "c2s.h" + +/** sx features callback */ +static void _bind_features(sx_t s, sx_plugin_t p, nad_t nad) { + int ns; + + if(s->auth_id == NULL) { + log_debug(ZONE, "not auth'd, not offering resource bind"); + + return; + } + + log_debug(ZONE, "offering resource bind and session"); + + ns = nad_add_namespace(nad, uri_BIND, NULL); + nad_append_elem(nad, ns, "bind", 1); + + ns = nad_add_namespace(nad, uri_XSESSION, NULL); + nad_append_elem(nad, ns, "session", 1); +} + +/** plugin initialiser */ +int bind_init(sx_env_t env, sx_plugin_t p, va_list args) { + log_debug(ZONE, "initialising resource bind sx plugin"); + + p->features = _bind_features; + + return 0; +} diff --git a/c2s/c2s.c b/c2s/c2s.c new file mode 100644 index 00000000..d2e7aaa5 --- /dev/null +++ b/c2s/c2s.c @@ -0,0 +1,1070 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "c2s.h" + +static int _c2s_client_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + sess_t sess = (sess_t) arg; + sx_buf_t buf = (sx_buf_t) data; + int rlen, len, ns, elem, attr, i, r; + sx_error_t *sxe; + nad_t nad; + char root[9]; + + switch(e) { + case event_WANT_READ: + log_debug(ZONE, "want read"); + mio_read(sess->c2s->mio, sess->fd); + break; + + case event_WANT_WRITE: + log_debug(ZONE, "want write"); + mio_write(sess->c2s->mio, sess->fd); + break; + + case event_READ: + log_debug(ZONE, "reading from %d", sess->fd); + + /* check rate limits */ + if(sess->rate != NULL) { + if(rate_check(sess->rate) == 0) { + + /* inform the app if we haven't already */ + if(!sess->rate_log) { + if(s->state >= state_STREAM && sess->jid != NULL) + log_write(sess->c2s->log, LOG_NOTICE, "[%d] [%s] is being byte rate limited", sess->fd, sess->jid); + else + log_write(sess->c2s->log, LOG_NOTICE, "[%d] [%s, port=%d] is being byte rate limited", sess->fd, sess->ip, sess->port); + + sess->rate_log = 1; + } + + log_debug(ZONE, "%d is throttled, delaying read", sess->fd); + + buf->len = 0; + return 0; + } + + /* find out how much we can have */ + rlen = rate_left(sess->rate); + if(rlen > buf->len) + rlen = buf->len; + } + + /* do the read */ + len = recv(sess->fd, buf->data, buf->len, 0); + + if(len < 0) { + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) { + buf->len = 0; + return 0; + } + + if(s->state >= state_STREAM && sess->jid != NULL) + log_write(sess->c2s->log, LOG_NOTICE, "[%d] [%s] read error: %s (%d)", sess->fd, jid_full(sess->jid), strerror(errno), errno); + else + log_write(sess->c2s->log, LOG_NOTICE, "[%d] [%s, port=%d] read error: %s (%d)", sess->fd, sess->ip, sess->port, strerror(errno), errno); + + sx_kill(s); + + return -1; + } + + else if(len == 0) { + /* they went away */ + sx_kill(s); + + return -1; + } + + log_debug(ZONE, "read %d bytes", len); + + buf->len = len; + + return len; + + case event_WRITE: + log_debug(ZONE, "writing to %d", sess->fd); + + len = send(sess->fd, buf->data, buf->len, 0); + if(len >= 0) { + log_debug(ZONE, "%d bytes written", len); + return len; + } + + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) + return 0; + + if(s->state >= state_OPEN && sess->jid != NULL) + log_write(sess->c2s->log, LOG_NOTICE, "[%d] [%s] write error: %s (%d)", sess->fd, jid_full(sess->jid), strerror(errno), errno); + else + log_write(sess->c2s->log, LOG_NOTICE, "[%d] [%s. port=%d] write error: %s (%d)", sess->fd, sess->ip, sess->port, strerror(errno), errno); + + sx_kill(s); + + return -1; + + case event_ERROR: + sxe = (sx_error_t *) data; + if(sess->jid != NULL) + log_write(sess->c2s->log, LOG_NOTICE, "[%d] [%s] error: %s (%s)", sess->fd, jid_full(sess->jid), sxe->generic, sxe->specific); + else + log_write(sess->c2s->log, LOG_NOTICE, "[%d] [%s, port=%d] error: %s (%s)", sess->fd, sess->ip, sess->port, sxe->generic, sxe->specific); + + break; + + case event_STREAM: + + if(s->req_to == NULL) { + log_debug(ZONE, "no stream to provided, closing"); + sx_error(s, stream_err_HOST_UNKNOWN, "no 'to' attribute on stream header"); + sx_close(s); + + return 0; + } + + /* setup the realm */ + sess->realm = xhash_get(sess->c2s->realms, s->req_to); + + if(sess->realm == NULL) { + log_debug(ZONE, "no service available for requested domain '%s'", s->req_to); + sx_error(s, stream_err_HOST_UNKNOWN, "service requested for unknown domain"); + sx_close(s); + + return 0; + } + + if(xhash_get(sess->c2s->sm_avail, s->req_to) == NULL) { + log_debug(ZONE, "sm for domain '%s' is not online", s->req_to); + sx_error(s, stream_err_HOST_GONE, "session manager for requested domain is not available"); + sx_close(s); + + return 0; + } + + break; + + case event_PACKET: + nad = (nad_t) data; + + /* we only want (message|presence|iq) in jabber:client, everything else gets dropped */ + snprintf(root, 9, "%.*s", NAD_ENAME_L(nad, 0), NAD_ENAME(nad, 0)); + if(NAD_ENS(nad, 0) != nad_find_namespace(nad, 0, uri_CLIENT, NULL) || + (strcmp(root, "message") != 0 && strcmp(root, "presence") != 0 && strcmp(root, "iq") != 0)) { + nad_free(nad); + return 0; + } + + /* pre-session requests */ + if(!sess->active && sess->sasl_authd && sess->result == NULL && strcmp(root, "iq") == 0 && nad_find_attr(nad, 0, -1, "type", "set") >= 0) { + /* resource bind */ + if(!sess->bound && (ns = nad_find_scoped_namespace(nad, uri_BIND, NULL)) >= 0 && (elem = nad_find_elem(nad, 0, ns, "bind", 1)) >= 0) { + sess->jid = jid_new(sess->c2s->pc, sess->s->auth_id, -1); + + /* get the resource */ + elem = nad_find_elem(nad, elem, ns, "resource", 1); + + /* user-specified resource */ + if(elem >= 0) { + char resource_buf[1024]; + + if(NAD_CDATA_L(nad, elem) == 0) { + log_debug(ZONE, "no resource specified on bind"); + sx_nad_write(sess->s, stanza_error(nad, 0, stanza_err_BAD_REQUEST)); + + return 0; + } + + /* Put resource into JID */ + snprintf(resource_buf, 1024, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + if (jid_reset_components(sess->jid,sess->jid->node,sess->jid->domain,resource_buf) == NULL) { + sess->jid = NULL; + + sx_nad_write(sess->s, stanza_error(nad, 0, stanza_err_BAD_REQUEST)); + + return 0; + } + + /* !!! xmpp-core-19 requires that the resource be unused, and that an + * error be returned if its not. this is hard for us todo, and + * might not be the right thing anyway (it basically gets rid + * of the session replacement functionality, though that is not + * as important now that resources can be generated). clarification + * sought from the xmppwg */ + } + + /* generated resource */ + else { + /* generate random resource */ + jid_random_part(sess->jid, jid_RESOURCE); + } + + log_write(sess->c2s->log, LOG_NOTICE, "[%d] bound: jid=%s", sess->s->tag, jid_full(sess->jid)); + + sess->bound = 1; + + sess->result = nad_new(sess->s->nad_cache); + + ns = nad_add_namespace(sess->result, uri_CLIENT, NULL); + + nad_append_elem(sess->result, ns, "iq", 0); + nad_set_attr(sess->result, 0, -1, "type", "result", 6); + + attr = nad_find_attr(nad, 0, -1, "id", NULL); + if(attr >= 0) + nad_set_attr(sess->result, 0, -1, "id", NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + ns = nad_add_namespace(sess->result, uri_BIND, NULL); + + nad_append_elem(sess->result, ns, "bind", 1); + nad_append_elem(sess->result, ns, "jid", 2); + nad_append_cdata(sess->result, jid_full(sess->jid), strlen(jid_full(sess->jid)), 3); + + sx_nad_write(sess->s, stanza_tofrom(sess->result, 0)); + + sess->result = NULL; + + nad_free(nad); + + return 0; + } + + /* new-style session request */ + else if(sess->bound && (ns = nad_find_scoped_namespace(nad, uri_XSESSION, NULL)) >= 0 && (elem = nad_find_elem(nad, 0, ns, "session", 1)) >= 0) { + /* our local id */ + sprintf(sess->c2s_id, "%d", sess->s->tag); + + log_write(sess->c2s->log, LOG_NOTICE, "[%d] requesting session: jid=%s", sess->s->tag, jid_full(sess->jid)); + + /* build a result packet, we'll send this back to the client after we have a session for them */ + sess->result = nad_new(sess->s->nad_cache); + + ns = nad_add_namespace(sess->result, uri_CLIENT, NULL); + + nad_append_elem(sess->result, ns, "iq", 0); + nad_set_attr(sess->result, 0, -1, "type", "result", 6); + + attr = nad_find_attr(nad, 0, -1, "id", NULL); + if(attr >= 0) + nad_set_attr(sess->result, 0, -1, "id", NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + /* start a session with the sm */ + sm_start(sess); + + /* finished with the nad */ + nad_free(nad); + + /* handled */ + return 0; + } + + log_debug(ZONE, "unrecognised pre-session packet, bye"); + log_write(sess->c2s->log, LOG_NOTICE, "[%d] unrecognized pre-session packet, closing stream", sess->s->tag); + + sx_error(s, stream_err_NOT_AUTHORIZED, "unrecognized pre-session stanza"); + sx_close(s); + + nad_free(nad); + return 0; + } + +#ifdef HAVE_SSL + /* drop packets if they have to starttls and they haven't */ + if((sess->s->flags & SX_SSL_STARTTLS_REQUIRE) && sess->s->ssf == 0) { + nad_free(nad); + return 0; + } +#endif + + /* handle iq:auth packets */ + if(authreg_process(sess->c2s, sess, nad) == 0) + return 0; + + /* drop it if no session */ + if(!sess->active) { + log_debug(ZONE, "pre-session packet, bye"); + log_write(sess->c2s->log, LOG_NOTICE, "[%d] packet sent before session start, closing stream", sess->s->tag); + + sx_error(s, stream_err_NOT_AUTHORIZED, "stanza sent before session start"); + sx_close(s); + + nad_free(nad); + return 0; + } + + /* pass it on to the session manager */ + sm_packet(sess, nad); + + break; + + case event_OPEN: + + /* only send a result and bring us online if this wasn't a sasl auth */ + if(strlen(s->auth_method) < 4 || strncmp("SASL", s->auth_method, 4) != 0) { + /* return the auth result to the client */ + sx_nad_write(s, sess->result); + sess->result = NULL; + + /* we're good to go */ + sess->active = 1; + } + + /* they sasl auth'd, so we only want the new-style session start */ + else { + log_write(sess->c2s->log, LOG_NOTICE, "[%d] SASL authentication succeeded: mechanism=%s; authzid=%s", sess->s->tag, &sess->s->auth_method[5], sess->s->auth_id); + sess->sasl_authd = 1; + } + + break; + + case event_CLOSED: + mio_close(sess->c2s->mio, sess->fd); + + break; + } + + return 0; +} + +static int _c2s_client_accept_check(c2s_t c2s, int fd, char *ip) { + rate_t rt; + + if(access_check(c2s->access, ip) == 0) { + log_write(c2s->log, LOG_NOTICE, "[%d] [%s] access denied by configuration", fd, ip); + return 1; + } + + if(c2s->conn_rate_total != 0) { + rt = (rate_t) xhash_get(c2s->conn_rates, ip); + if(rt == NULL) { + rt = rate_new(c2s->conn_rate_total, c2s->conn_rate_seconds, c2s->conn_rate_wait); + xhash_put(c2s->conn_rates, pstrdup(xhash_pool(c2s->conn_rates), ip), (void *) rt); + pool_cleanup(xhash_pool(c2s->conn_rates), (void (*)(void *)) rate_free, rt); + } + + if(rate_check(rt) == 0) { + log_write(c2s->log, LOG_NOTICE, "[%d] [%s] is being rate limited", fd, ip); + return 1; + } + + rate_add(rt, 1); + } + + return 0; +} + +static int _c2s_client_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) { + sess_t sess = (sess_t) arg; + c2s_t c2s = (c2s_t) arg; + struct sockaddr_storage sa; + int namelen = sizeof(sa), port, nbytes; + + switch(a) { + case action_READ: + log_debug(ZONE, "read action on fd %d", fd); + + /* they did something */ + sess->last_activity = time(NULL); + + ioctl(fd, FIONREAD, &nbytes); + if(nbytes == 0) { + sx_kill(sess->s); + return 0; + } + + return sx_can_read(sess->s); + + case action_WRITE: + log_debug(ZONE, "write action on fd %d", fd); + + return sx_can_write(sess->s); + + case action_CLOSE: + log_debug(ZONE, "close action on fd %d", fd); + + log_write(sess->c2s->log, LOG_NOTICE, "[%d] [%s, port=%d] disconnect", sess->fd, sess->ip, sess->port); + + /* tell the sm to close their session */ + if(sess->active) + sm_end(sess); + + jqueue_push(sess->c2s->dead, (void *) sess->s, 0); + + xhash_zap(sess->c2s->sessions, sess->skey); + + jqueue_push(sess->c2s->dead_sess, (void *) sess, 0); + + break; + + case action_ACCEPT: + log_debug(ZONE, "accept action on fd %d", fd); + + getpeername(fd, (struct sockaddr *) &sa, &namelen); + port = j_inet_getport(&sa); + + log_write(c2s->log, LOG_NOTICE, "[%d] [%s, port=%d] connect", fd, (char *) data, port); + + if(_c2s_client_accept_check(c2s, fd, (char *) data) != 0) + return 1; + + sess = (sess_t) malloc(sizeof(struct sess_st)); + memset(sess, 0, sizeof(struct sess_st)); + + sess->c2s = c2s; + + sess->fd = fd; + + sess->ip = strdup((char *) data); + sess->port = port; + + /* they did something */ + sess->last_activity = time(NULL); + + sess->s = sx_new(c2s->sx_env, fd, _c2s_client_sx_callback, (void *) sess); + mio_app(m, fd, _c2s_client_mio_callback, (void *) sess); + + if(c2s->byte_rate_total != 0) + sess->rate = rate_new(c2s->byte_rate_total, c2s->byte_rate_seconds, c2s->byte_rate_wait); + + /* find out which port this is */ + getsockname(fd, (struct sockaddr *) &sa, &namelen); + port = j_inet_getport(&sa); + + /* remember it */ + sprintf(sess->skey, "%d", fd); + xhash_put(c2s->sessions, sess->skey, (void *) sess); + +#ifdef HAVE_SSL + /* go ssl wrappermode if they're on the ssl port */ + if(port == c2s->local_ssl_port) + sx_server_init(sess->s, SX_SSL_WRAPPER | SX_SASL_OFFER); + else + sx_server_init(sess->s, ((c2s->local_pemfile != NULL) ? SX_SSL_STARTTLS_OFFER : 0) | SX_SASL_OFFER | + (c2s->local_require_starttls ? SX_SSL_STARTTLS_REQUIRE : 0)); +#else + sx_server_init(sess->s, SX_SASL_OFFER); +#endif + break; + } + + return 0; +} + +static void _c2s_component_presence(c2s_t c2s, nad_t nad) { + int attr; + char from[1024]; + sess_t sess; + union xhashv xhv; + + if((attr = nad_find_attr(nad, 0, -1, "from", NULL)) < 0) { + nad_free(nad); + return; + } + + strncpy(from, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + from[NAD_AVAL_L(nad, attr)] = '\0'; + + if(nad_find_attr(nad, 0, -1, "type", NULL) < 0) { + log_debug(ZONE, "component available from '%s'", from); + + if(xhash_get(c2s->realms, from) != NULL) { + log_debug(ZONE, "sm for serviced domain '%s' online", from); + + xhash_put(c2s->sm_avail, pstrdup(xhash_pool(c2s->realms), from), (void *) 1); + } + + nad_free(nad); + return; + } + + if(nad_find_attr(nad, 0, -1, "type", "unavailable") < 0) { + nad_free(nad); + return; + } + + log_debug(ZONE, "component unavailable from '%s'", from); + + if(xhash_get(c2s->sm_avail, from) != NULL) { + log_debug(ZONE, "sm for serviced domain '%s' offline", from); + + if(xhash_iter_first(c2s->sessions)) + do { + xhv.sess_val = &sess; + xhash_iter_get(c2s->sessions, NULL, xhv.val); + + if(sess->jid != NULL && strcmp(sess->jid->domain, from) == 0) { + log_debug(ZONE, "killing session %s", jid_full(sess->jid)); + + sess->active = 0; + sx_close(sess->s); + } + } while(xhash_iter_next(c2s->sessions)); + + xhash_zap(c2s->sm_avail, from); + } +} + +int c2s_router_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + c2s_t c2s = (c2s_t) arg; + sx_buf_t buf = (sx_buf_t) data; + sx_error_t *sxe; + nad_t nad; + int len, elem, from, c2sid, smid, action, id, ns, attr, scan, replaced; + char skey[10]; + sess_t sess; + + switch(e) { + case event_WANT_READ: + log_debug(ZONE, "want read"); + mio_read(c2s->mio, c2s->fd); + break; + + case event_WANT_WRITE: + log_debug(ZONE, "want write"); + mio_write(c2s->mio, c2s->fd); + break; + + case event_READ: + log_debug(ZONE, "reading from %d", c2s->fd); + + /* do the read */ + len = recv(c2s->fd, buf->data, buf->len, 0); + + if(len < 0) { + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) { + buf->len = 0; + return 0; + } + + log_write(c2s->log, LOG_NOTICE, "[%d] [router] read error: %s (%d)", c2s->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + } + + else if(len == 0) { + /* they went away */ + sx_kill(s); + + return -1; + } + + log_debug(ZONE, "read %d bytes", len); + + buf->len = len; + + return len; + + case event_WRITE: + log_debug(ZONE, "writing to %d", c2s->fd); + + len = send(c2s->fd, buf->data, buf->len, 0); + if(len >= 0) { + log_debug(ZONE, "%d bytes written", len); + return len; + } + + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) + return 0; + + log_write(c2s->log, LOG_NOTICE, "[%d] [router] write error: %s (%d)", c2s->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + + case event_ERROR: + sxe = (sx_error_t *) data; + log_write(c2s->log, LOG_NOTICE, "error from router: %s (%s)", sxe->generic, sxe->specific); + + if(sxe->code == SX_ERR_AUTH) + sx_close(s); + + break; + + case event_STREAM: + break; + + case event_OPEN: + log_write(c2s->log, LOG_NOTICE, "connection to router established"); + + /* reset connection attempts counter */ + c2s->retry_left = c2s->retry_init; + + nad = nad_new(c2s->router->nad_cache); + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, ns, "bind", 0); + nad_append_attr(nad, -1, "name", c2s->id); + + log_debug(ZONE, "requesting component bind for '%s'", c2s->id); + + sx_nad_write(c2s->router, nad); + + return 0; + + case event_PACKET: + nad = (nad_t) data; + + /* drop unqualified packets */ + if(NAD_ENS(nad, 0) < 0) { + nad_free(nad); + return 0; + } + + /* watch for the features packet */ + if(s->state == state_STREAM) { + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_STREAMS) || strncmp(uri_STREAMS, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_STREAMS)) != 0 || NAD_ENAME_L(nad, 0) != 8 || strncmp("features", NAD_ENAME(nad, 0), 8) != 0) { + log_debug(ZONE, "got a non-features packet on an unauth'd stream, dropping"); + nad_free(nad); + return 0; + } + +#ifdef HAVE_SSL + /* starttls if we can */ + if(c2s->sx_ssl != NULL && c2s->router_pemfile != NULL && s->ssf == 0) { + ns = nad_find_scoped_namespace(nad, uri_TLS, NULL); + if(ns >= 0) { + elem = nad_find_elem(nad, 0, ns, "starttls", 1); + if(elem >= 0) { + if(sx_ssl_client_starttls(c2s->sx_ssl, s, c2s->router_pemfile) == 0) { + nad_free(nad); + return 0; + } + log_write(c2s->log, LOG_ERR, "unable to establish encrypted session with router"); + } + } + } +#endif + + /* !!! pull the list of mechanisms, and choose the best one. + * if there isn't an appropriate one, error and bail */ + + /* authenticate */ + sx_sasl_auth(c2s->sx_sasl, s, "jabberd-router", "DIGEST-MD5", c2s->router_user, c2s->router_pass); + + nad_free(nad); + return 0; + } + + /* watch for the bind response */ + if(s->state == state_OPEN && !c2s->online) { + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_COMPONENT) || strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) != 0 || NAD_ENAME_L(nad, 0) != 4 || strncmp("bind", NAD_ENAME(nad, 0), 4) != 0) { + log_debug(ZONE, "got a packet from router, but we're not online, dropping"); + nad_free(nad); + return 0; + } + + /* catch errors */ + attr = nad_find_attr(nad, 0, -1, "error", NULL); + if(attr >= 0) { + log_write(c2s->log, LOG_ERR, "router refused bind request (%.*s)", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + exit(1); + } + + log_debug(ZONE, "coming online"); + + /* if we're coming online for the first time, setup listening sockets */ +#ifdef HAVE_SSL + if(c2s->server_fd == 0 && c2s->server_ssl_fd == 0) { +#else + if(c2s->server_fd == 0) { +#endif + if(c2s->local_port != 0) { + c2s->server_fd = mio_listen(c2s->mio, c2s->local_port, c2s->local_ip, _c2s_client_mio_callback, (void *) c2s); + if(c2s->server_fd < 0) + log_write(c2s->log, LOG_ERR, "[%s, port=%d] failed to listen", c2s->local_ip, c2s->local_port); + else + log_write(c2s->log, LOG_NOTICE, "[%s, port=%d] listening for connections", c2s->local_ip, c2s->local_port); + } else + c2s->server_fd = -1; + +#ifdef HAVE_SSL + if(c2s->local_ssl_port != 0 && c2s->local_pemfile != NULL) { + c2s->server_ssl_fd = mio_listen(c2s->mio, c2s->local_ssl_port, c2s->local_ip, _c2s_client_mio_callback, (void *) c2s); + if(c2s->server_ssl_fd < 0) + log_write(c2s->log, LOG_ERR, "[%s, port=%d] failed to listen", c2s->local_ip, c2s->local_ssl_port); + else + log_write(c2s->log, LOG_NOTICE, "[%s, port=%d] listening for SSL connections", c2s->local_ip, c2s->local_ssl_port); + } else + c2s->server_ssl_fd = -1; +#endif + } + +#ifdef HAVE_SSL + if(c2s->server_fd < 0 && c2s->server_ssl_fd < 0) { + log_write(c2s->log, LOG_ERR, "both normal and SSL ports are disabled, nothing to do!"); +#else + if(c2s->server_fd < 0) { + log_write(c2s->log, LOG_ERR, "server port is disabled, nothing to do!"); +#endif + exit(1); + } + + /* we're online */ + c2s->online = c2s->started = 1; + log_write(c2s->log, LOG_NOTICE, "ready for connections", c2s->id); + + nad_free(nad); + return 0; + } + + /* need component packets */ + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_COMPONENT) || strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) != 0) { + log_debug(ZONE, "wanted component packet, dropping"); + nad_free(nad); + return 0; + } + + /* component presence */ + if(NAD_ENAME_L(nad, 0) == 8 && strncmp("presence", NAD_ENAME(nad, 0), 8) == 0) { + _c2s_component_presence(c2s, nad); + return 0; + } + + /* we want route */ + if(NAD_ENAME_L(nad, 0) != 5 || strncmp("route", NAD_ENAME(nad, 0), 5) != 0) { + log_debug(ZONE, "wanted {component}route, dropping"); + nad_free(nad); + return 0; + } + + /* only handle unicasts */ + if(nad_find_attr(nad, 0, -1, "type", NULL) >= 0) { + log_debug(ZONE, "non-unicast packet, dropping"); + nad_free(nad); + return 0; + } + + /* need some payload */ + if(nad->ecur == 1) { + log_debug(ZONE, "no route payload, dropping"); + nad_free(nad); + return 0; + } + + ns = nad_find_namespace(nad, 1, uri_SESSION, NULL); + if(ns < 0) { + log_debug(ZONE, "not a c2s packet, dropping"); + nad_free(nad); + return 0; + } + + /* figure out the session */ + c2sid = nad_find_attr(nad, 1, ns, "c2s", NULL); + if(c2sid < 0) { + log_debug(ZONE, "no c2s id on payload, dropping"); + nad_free(nad); + return 0; + } + snprintf(skey, 10, "%.*s", NAD_AVAL_L(nad, c2sid), NAD_AVAL(nad, c2sid)); + + /* find the session, quietly drop if we don't have it */ + sess = xhash_get(c2s->sessions, skey); + if(sess == NULL) { + /* !!! might want to send a stop to the sm; maybe it thinks we're still here? */ + log_debug(ZONE, "no session for %s", skey); + + nad_free(nad); + return 0; + } + + /* if they're pre-stream, then this is leftovers from a previous session */ + if(sess->s->state < state_STREAM) { + log_debug(ZONE, "session %s is pre-stream", skey); + + nad_free(nad); + return 0; + } + + /* check the sm session id if they gave us one */ + smid = nad_find_attr(nad, 1, ns, "sm", NULL); + if(smid >= 0 && sess->sm_id[0] != '\0' && (strlen(sess->sm_id) != NAD_AVAL_L(nad, smid) || strncmp(sess->sm_id, NAD_AVAL(nad, smid), NAD_AVAL_L(nad, smid)) != 0)) { + log_debug(ZONE, "expected packet from sm session %s, but got one from %.*s, dropping", sess->sm_id, NAD_AVAL_L(nad, smid), NAD_AVAL(nad, smid)); + nad_free(nad); + return 0; + } + + /* it has to have come from the session manager */ + from = nad_find_attr(nad, 0, -1, "from", NULL); + if(strlen(sess->s->req_to) != NAD_AVAL_L(nad, from) || strncmp(sess->s->req_to, NAD_AVAL(nad, from), NAD_AVAL_L(nad, from)) != 0) { + log_debug(ZONE, "packet from '%.*s' for %s, but they're not the sm for this sess", NAD_AVAL_L(nad, from), NAD_AVAL(nad, from), skey); + nad_free(nad); + return 0; + } + + /* route errors */ + if(nad_find_attr(nad, 0, -1, "error", NULL) >= 0) { + log_debug(ZONE, "routing error"); + + /* !!! kill the session */ + + nad_free(nad); + return 0; + } + + /* session control packets */ + if(NAD_ENS(nad, 1) == ns) { + action = nad_find_attr(nad, 1, -1, "action", NULL); + id = nad_find_attr(nad, 1, -1, "id", NULL); + + /* failed requests */ + if(nad_find_attr(nad, 1, ns, "failed", NULL) >= 0) { + /* make sure the id matches */ + if(id < 0 || sess->sm_request[0] == '\0' || strlen(sess->sm_request) != NAD_AVAL_L(nad, id) || strncmp(sess->sm_request, NAD_AVAL(nad, id), NAD_AVAL_L(nad, id)) != 0) { + if(id >= 0) { + log_debug(ZONE, "got a response with id %.*s, but we were expecting %s", NAD_AVAL_L(nad, id), NAD_AVAL(nad, id), sess->sm_request); + } else { + log_debug(ZONE, "got a response with no id, but we were expecting %s", sess->sm_request); + } + + nad_free(nad); + return 0; + } + + /* handled request */ + sess->sm_request[0] = '\0'; + + /* we only care about failed start and create */ + if((NAD_AVAL_L(nad, action) == 5 && strncmp("start", NAD_AVAL(nad, action), 5) == 0) || + (NAD_AVAL_L(nad, action) == 6 && strncmp("create", NAD_AVAL(nad, action), 6) == 0)) { + + /* create failed, so we need to remove them from authreg */ + if(NAD_AVAL_L(nad, action) == 6 && c2s->ar->delete_user != NULL) { + if((c2s->ar->delete_user)(c2s->ar, sess->jid->node, sess->realm) != 0) + log_write(c2s->log, LOG_NOTICE, "[%d] user creation failed, and unable to delete user credentials: user=%s, realm=%s", sess->s->tag, sess->jid->node, sess->realm); + else + log_write(c2s->log, LOG_NOTICE, "[%d] user creation failed, so deleted user credentials: user=%s, realm=%s", sess->s->tag, sess->jid->node, sess->realm); + } + + /* error the result and return it to the client */ + sx_nad_write(sess->s, stanza_error(sess->result, 0, stanza_err_INTERNAL_SERVER_ERROR)); + sess->result = NULL; + + jid_free(sess->jid); + sess->jid = NULL; + + nad_free(nad); + return 0; + } + + log_debug(ZONE, "weird, got a failed session response, with a matching id, but the action is bogus *shrug*"); + + nad_free(nad); + return 0; + } + + /* if we're not active yet, then we only want "started" or "created" responses */ + if(!sess->active) { + /* make sure the id matches */ + if(id < 0 || sess->sm_request[0] == '\0' || strlen(sess->sm_request) != NAD_AVAL_L(nad, id) || strncmp(sess->sm_request, NAD_AVAL(nad, id), NAD_AVAL_L(nad, id)) != 0) { + if(id >= 0) { + log_debug(ZONE, "got a response with id %.*s, but we were expecting %s", NAD_AVAL_L(nad, id), NAD_AVAL(nad, id), sess->sm_request); + } else { + log_debug(ZONE, "got a response with no id, but we were expecting %s", sess->sm_request); + } + + nad_free(nad); + return 0; + } + + /* session started */ + if(NAD_AVAL_L(nad, action) == 7 && strncmp("started", NAD_AVAL(nad, action), 7) == 0) { + /* handled request */ + sess->sm_request[0] = '\0'; + + /* copy the sm id */ + if(smid >= 0) + snprintf(sess->sm_id, 41, "%.*s", NAD_AVAL_L(nad, smid), NAD_AVAL(nad, smid)); + + nad_free(nad); + + /* bring them online, old-skool */ + if(!sess->sasl_authd) { + sx_auth(sess->s, "traditional", jid_user(sess->jid)); + return 0; + } + + /* return the auth result to the client */ + sx_nad_write(sess->s, sess->result); + sess->result = NULL; + + /* we're good to go */ + sess->active = 1; + + return 0; + } + + /* user created */ + if(NAD_AVAL_L(nad, action) == 7 && strncmp("created", NAD_AVAL(nad, action), 7) == 0) { + /* handled request */ + sess->sm_request[0] = '\0'; + + nad_free(nad); + + /* return the result to the client */ + sx_nad_write(sess->s, sess->result); + sess->result = NULL; + + return 0; + } + + /* anything else gets thrown out */ + log_debug(ZONE, "got a packet for %s, but they don't have an active session yes", jid_full(sess->jid)); + + nad_free(nad); + + return 0; + } + + /* end responses */ + + /* !!! this "replaced" stuff is a hack - its really a subaction of "ended". + * hurrah, another control protocol rewrite is needed :( + */ + + replaced = 0; + if(NAD_AVAL_L(nad, action) == 8 && strncmp("replaced", NAD_AVAL(nad, action), NAD_AVAL_L(nad, action)) == 0) + replaced = 1; + if(replaced || (NAD_AVAL_L(nad, action) == 5 && strncmp("ended", NAD_AVAL(nad, action), NAD_AVAL_L(nad, action)) == 0)) { + sess->active = 0; + + if(replaced) + sx_error(sess->s, stream_err_CONFLICT, NULL); + + /* close them */ + sx_close(sess->s); + + nad_free(nad); + return 0; + } + + /* make sure the id matches */ + if(id < 0 || sess->sm_request[0] == '\0' || strncmp(sess->sm_request, NAD_AVAL(nad, id), NAD_AVAL_L(nad, id)) != 0) { + if(id >= 0) { + log_debug(ZONE, "got a response with id %.*s, but we were expecting %s", NAD_AVAL_L(nad, id), NAD_AVAL(nad, id), sess->sm_request); + } else { + log_debug(ZONE, "got a response with no id, but we were expecting %s", sess->sm_request); + } + + nad_free(nad); + return 0; + } + + /* handled request */ + sess->sm_request[0] = '\0'; + + log_debug(ZONE, "unknown action %.*s", NAD_AVAL_L(nad, id), NAD_AVAL(nad, id)); + + nad_free(nad); + + return 0; + } + + /* client packets */ + if(NAD_NURI_L(nad, NAD_ENS(nad, 1)) == strlen(uri_CLIENT) && strncmp(uri_CLIENT, NAD_NURI(nad, NAD_ENS(nad, 1)), strlen(uri_CLIENT)) == 0) { + if(!sess->active) { + /* its a strange world .. */ + nad_free(nad); + return 0; + } + + /* sm is bouncing something */ + if(nad_find_attr(nad, 1, ns, "failed", NULL) >= 0) { + /* there's really no graceful way to handle this */ + sx_error(s, stream_err_INTERNAL_SERVER_ERROR, "session manager failed control action"); + sx_close(s); + + nad_free(nad); + return 0; + } + + /* remove sm specifics */ + nad_set_attr(nad, 1, ns, "c2s", NULL, 0); + nad_set_attr(nad, 1, ns, "sm", NULL, 0); + + /* forget about the internal namespace too */ + if(nad->elems[1].ns == ns) + nad->elems[1].ns = nad->nss[ns].next; + + else { + for(scan = nad->elems[1].ns; nad->nss[scan].next != -1 && nad->nss[scan].next != ns; scan = nad->nss[scan].next); + + /* got it */ + if(nad->nss[scan].next != -1) + nad->nss[scan].next = nad->nss[ns].next; + } + + sx_nad_write_elem(sess->s, nad, 1); + + return 0; + } + + /* its something else */ + log_debug(ZONE, "unknown packet, dropping"); + + nad_free(nad); + return 0; + + case event_CLOSED: + mio_close(c2s->mio, c2s->fd); + break; + } + + return 0; +} + +int c2s_router_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) { + c2s_t c2s = (c2s_t) arg; + int nbytes; + + switch(a) { + case action_READ: + log_debug(ZONE, "read action on fd %d", fd); + + ioctl(fd, FIONREAD, &nbytes); + if(nbytes == 0) { + sx_kill(c2s->router); + return 0; + } + + return sx_can_read(c2s->router); + + case action_WRITE: + log_debug(ZONE, "write action on fd %d", fd); + return sx_can_write(c2s->router); + + case action_CLOSE: + log_debug(ZONE, "close action on fd %d", fd); + log_write(c2s->log, LOG_NOTICE, "connection to router closed"); + + c2s_lost_router = 1; + + /* we're offline */ + c2s->online = 0; + + break; + + case action_ACCEPT: + break; + } + + return 0; +} diff --git a/c2s/c2s.h b/c2s/c2s.h new file mode 100644 index 00000000..3f7800b7 --- /dev/null +++ b/c2s/c2s.h @@ -0,0 +1,286 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "mio/mio.h" +#include "sx/sx.h" +#include "sx/ssl.h" +#include "sx/sasl.h" +#include "util/util.h" + +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif + +/* forward decl */ +typedef struct c2s_st *c2s_t; +typedef struct sess_st *sess_t; +typedef struct authreg_st *authreg_t; + +struct sess_st { + c2s_t c2s; + + int fd; + + char skey[10]; + + char *ip; + int port; + + sx_t s; + + rate_t rate; + int rate_log; + + time_t last_activity; + + char *realm; + + int bound; + int active; + + nad_t result; + + int sasl_authd; /* 1 = they did a sasl auth */ + + /** full jid of the session */ + jid_t jid; + + /** session id for us and them */ + char c2s_id[10], sm_id[41]; + + /** this holds the id of the current pending SM request */ + char sm_request[41]; +}; + +/* allowed mechanisms */ +#define AR_MECH_TRAD_PLAIN (1<<0) +#define AR_MECH_TRAD_DIGEST (1<<1) +#define AR_MECH_TRAD_ZEROK (1<<2) + +struct c2s_st { + /** our id (hostname) with the router */ + char *id; + + /** how to connect to the router */ + char *router_ip; + int router_port; + char *router_user; + char *router_pass; + char *router_pemfile; + + /** mio context */ + mio_t mio; + + /** sessions */ + xht sessions; + + /** sx environment */ + sx_env_t sx_env; + sx_plugin_t sx_ssl; + sx_plugin_t sx_sasl; + + /** router's conn */ + sx_t router; + int fd; + + /** listening sockets */ + int server_fd; +#ifdef HAVE_SSL + int server_ssl_fd; +#endif + + /** config */ + config_t config; + + /** logging */ + log_t log; + + /** log data */ + log_type_t log_type; + char *log_facility; + char *log_ident; + + /** connect retry */ + int retry_init; + int retry_lost; + int retry_sleep; + int retry_left; + + /** ip to listen on */ + char *local_ip; + + /** unencrypted port */ + int local_port; + + /** starttls pemfile */ + char *local_pemfile; + + /** require starttls */ + int local_require_starttls; + + /** certificate chain */ + char *local_cachain; + + /** verify-mode */ + int local_verify_mode; + + /** encrypted port */ + int local_ssl_port; + + /** max file descriptors */ + int io_max_fds; + + /** time checks */ + int io_check_interval; + int io_check_idle; + int io_check_keepalive; + + time_t next_check; + + /** auth/reg module */ + char *ar_module_name; + authreg_t ar; + + /** registration */ + int ar_register_enable; + char *ar_register_instructions; + int ar_register_password; + + /** allowed mechanisms */ + int ar_mechanisms; + + /** connection rates */ + int conn_rate_total; + int conn_rate_seconds; + int conn_rate_wait; + + xht conn_rates; + + /** byte rates (karma) */ + int byte_rate_total; + int byte_rate_seconds; + int byte_rate_wait; + + /** stringprep cache */ + prep_cache_t pc; + + /** access controls */ + access_t access; + + /** list of sx_t on the way out */ + jqueue_t dead; + + /** list of sess on the way out */ + jqueue_t dead_sess; + + /** this is true if we've connected to the router at least once */ + int started; + + /** true if we're bound in the router */ + int online; + + /** domain -> auth realm mapping */ + xht realms; + + /** availability of sms that we are servicing */ + xht sm_avail; +}; + +extern sig_atomic_t c2s_lost_router; + +int c2s_router_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg); +int c2s_router_sx_callback(sx_t s, sx_event_t e, void *data, void *arg); + +void sm_start(sess_t sess); +void sm_end(sess_t sess); +void sm_create(sess_t sess); +void sm_delete(sess_t sess); +void sm_packet(sess_t sess, nad_t nad); + +int bind_init(sx_env_t env, sx_plugin_t p, va_list args); + +struct authreg_st +{ + c2s_t c2s; + + /** module private data */ + void *private; + + /** returns 1 if the user exists, 0 if not */ + int (*user_exists)(authreg_t ar, char *username, char *realm); + + /** return this users cleartext password in the array (digest auth, password auth) */ + int (*get_password)(authreg_t ar, char *username, char *realm, char password[257]); + + /** check the given password against the stored password, 0 if equal, !0 if not equal (password auth) */ + int (*check_password)(authreg_t ar, char *username, char *realm, char password[257]); + + /** store this password (register) */ + int (*set_password)(authreg_t ar, char *username, char *realm, char password[257]); + + /** get/set zerok data for this user (zerok auth, zerok register) */ + int (*get_zerok)(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int *sequence); + int (*set_zerok)(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int sequence); + + /** make or break the user (register / register remove) */ + int (*create_user)(authreg_t ar, char *username, char *realm); + int (*delete_user)(authreg_t ar, char *username, char *realm); + + void (*free)(authreg_t ar); +}; + +/** get a handle for a single module */ +authreg_t authreg_init(c2s_t c2s, char *name); + +/** shut down */ +void authreg_free(authreg_t ar); + +/** type for the module init function */ +typedef int (*ar_module_init_fn)(authreg_t); + +/** the main authreg processor */ +int authreg_process(c2s_t c2s, sess_t sess, nad_t nad); + +/* +int authreg_user_exists(authreg_t ar, char *username, char *realm); +int authreg_get_password(authreg_t ar, char *username, char *realm, char password[257]); +int authreg_check_password(authreg_t ar, char *username, char *realm, char password[257]); +int authreg_set_password(authreg_t ar, char *username, char *realm, char password[257]); +int authreg_get_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int *sequence); +int authreg_set_zerok(authreg_t ar, char *username, char *realm, char hash[41], char token[11], int sequence); +int authreg_create_user(authreg_t ar, char *username, char *realm); +int authreg_delete_user(authreg_t ar, char *username, char *realm); +void authreg_free(authreg_t ar); +*/ + +/* union for xhash_iter_get to comply with strict-alias rules for gcc3 */ +union xhashv +{ + void **val; + char **char_val; + sess_t *sess_val; +}; diff --git a/c2s/main.c b/c2s/main.c new file mode 100644 index 00000000..ec1c9b3e --- /dev/null +++ b/c2s/main.c @@ -0,0 +1,715 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "c2s.h" + +#ifdef HAVE_IDN +#include +#endif + +static sig_atomic_t c2s_shutdown = 0; +sig_atomic_t c2s_lost_router = 0; +static sig_atomic_t c2s_logrotate = 0; + +static void _c2s_signal(int signum) +{ + c2s_shutdown = 1; + c2s_lost_router = 0; +} + +static void _c2s_signal_hup(int signum) +{ + c2s_logrotate = 1; +} + +/** store the process id */ +static void _c2s_pidfile(c2s_t c2s) { + char *pidfile; + FILE *f; + pid_t pid; + + pidfile = config_get_one(c2s->config, "pidfile", 0); + if(pidfile == NULL) + return; + + pid = getpid(); + + if((f = fopen(pidfile, "w+")) == NULL) { + log_write(c2s->log, LOG_ERR, "couldn't open %s for writing: %s", pidfile, strerror(errno)); + return; + } + + if(fprintf(f, "%d", pid) < 0) { + log_write(c2s->log, LOG_ERR, "couldn't write to %s: %s", pidfile, strerror(errno)); + return; + } + + fclose(f); + + log_write(c2s->log, LOG_INFO, "process id is %d, written to %s", pid, pidfile); +} +/** pull values out of the config file */ +static void _c2s_config_expand(c2s_t c2s) +{ + char *str, *ip, *mask; + config_elem_t elem; + int i; + + c2s->id = config_get_one(c2s->config, "id", 0); + if(c2s->id == NULL) + c2s->id = "c2s"; + + c2s->router_ip = config_get_one(c2s->config, "router.ip", 0); + if(c2s->router_ip == NULL) + c2s->router_ip = "127.0.0.1"; + + c2s->router_port = j_atoi(config_get_one(c2s->config, "router.port", 0), 5347); + + c2s->router_user = config_get_one(c2s->config, "router.user", 0); + if(c2s->router_user == NULL) + c2s->router_user = "jabberd"; + c2s->router_pass = config_get_one(c2s->config, "router.pass", 0); + if(c2s->router_pass == NULL) + c2s->router_pass = "secret"; + + c2s->router_pemfile = config_get_one(c2s->config, "router.pemfile", 0); + + c2s->retry_init = j_atoi(config_get_one(c2s->config, "router.retry.init", 0), 3); + c2s->retry_lost = j_atoi(config_get_one(c2s->config, "router.retry.lost", 0), 3); + if((c2s->retry_sleep = j_atoi(config_get_one(c2s->config, "router.retry.sleep", 0), 2)) < 1) + c2s->retry_sleep = 1; + + c2s->log_type = log_STDOUT; + if(config_get(c2s->config, "log") != NULL) { + if((str = config_get_attr(c2s->config, "log", 0, "type")) != NULL) { + if(strcmp(str, "file") == 0) + c2s->log_type = log_FILE; + else if(strcmp(str, "syslog") == 0) + c2s->log_type = log_SYSLOG; + } + } + + if(c2s->log_type == log_SYSLOG) { + c2s->log_facility = config_get_one(c2s->config, "log.facility", 0); + c2s->log_ident = config_get_one(c2s->config, "log.ident", 0); + if(c2s->log_ident == NULL) + c2s->log_ident = "jabberd/c2s"; + } else if(c2s->log_type == log_FILE) + c2s->log_ident = config_get_one(c2s->config, "log.file", 0); + + c2s->local_ip = config_get_one(c2s->config, "local.ip", 0); + if(c2s->local_ip == NULL) + c2s->local_ip = "0.0.0.0"; + + c2s->local_port = j_atoi(config_get_one(c2s->config, "local.port", 0), 0); + + c2s->local_pemfile = config_get_one(c2s->config, "local.pemfile", 0); + + if(config_get(c2s->config, "local.require-starttls") != NULL) + c2s->local_require_starttls = 1; + + c2s->local_cachain = config_get_one(c2s->config, "local.cachain", 0); + + c2s->local_verify_mode = j_atoi(config_get_one(c2s->config, "local.verify-mode", 0), 0); + + c2s->local_ssl_port = j_atoi(config_get_one(c2s->config, "local.ssl-port", 0), 0); + + c2s->io_max_fds = j_atoi(config_get_one(c2s->config, "io.max_fds", 0), 1024); + + c2s->io_check_interval = j_atoi(config_get_one(c2s->config, "io.check.interval", 0), 0); + c2s->io_check_idle = j_atoi(config_get_one(c2s->config, "io.check.idle", 0), 0); + c2s->io_check_keepalive = j_atoi(config_get_one(c2s->config, "io.check.keepalive", 0), 0); + + c2s->ar_module_name = config_get_one(c2s->config, "authreg.module", 0); + + c2s->ar_register_enable = (config_get(c2s->config, "authreg.register.enable") != NULL); + if(c2s->ar_register_enable) { + c2s->ar_register_instructions = config_get_one(c2s->config, "authreg.register.instructions", 0); + if(c2s->ar_register_instructions == NULL) + c2s->ar_register_instructions = "Enter a username and password to register with this server."; + } else + c2s->ar_register_password = (config_get(c2s->config, "authreg.register.password") != NULL); + + if(config_get(c2s->config, "authreg.mechanisms.traditional.plain") != NULL) c2s->ar_mechanisms |= AR_MECH_TRAD_PLAIN; + if(config_get(c2s->config, "authreg.mechanisms.traditional.digest") != NULL) c2s->ar_mechanisms |= AR_MECH_TRAD_DIGEST; + if(config_get(c2s->config, "authreg.mechanisms.traditional.zerok") != NULL) c2s->ar_mechanisms |= AR_MECH_TRAD_ZEROK; + + elem = config_get(c2s->config, "io.limits.bytes"); + if(elem != NULL) + { + c2s->byte_rate_total = j_atoi(elem->values[0], 0); + if(c2s->byte_rate_total != 0) + { + c2s->byte_rate_seconds = j_atoi(j_attr((const char **) elem->attrs[0], "seconds"), 1); + c2s->byte_rate_wait = j_atoi(j_attr((const char **) elem->attrs[0], "throttle"), 5); + } + } + + elem = config_get(c2s->config, "io.limits.connects"); + if(elem != NULL) + { + c2s->conn_rate_total = j_atoi(elem->values[0], 0); + if(c2s->conn_rate_total != 0) + { + c2s->conn_rate_seconds = j_atoi(j_attr((const char **) elem->attrs[0], "seconds"), 5); + c2s->conn_rate_wait = j_atoi(j_attr((const char **) elem->attrs[0], "throttle"), 5); + } + } + + str = config_get_one(c2s->config, "io.access.order", 0); + if(str == NULL || strcmp(str, "deny,allow") != 0) + c2s->access = access_new(0); + else + c2s->access = access_new(1); + + elem = config_get(c2s->config, "io.access.allow"); + if(elem != NULL) + { + for(i = 0; i < elem->nvalues; i++) + { + ip = j_attr((const char **) elem->attrs[i], "ip"); + mask = j_attr((const char **) elem->attrs[i], "mask"); + + if(ip == NULL) + continue; + + if(mask == NULL) + mask = "255.255.255.255"; + + access_allow(c2s->access, ip, mask); + } + } + + elem = config_get(c2s->config, "io.access.deny"); + if(elem != NULL) + { + for(i = 0; i < elem->nvalues; i++) + { + ip = j_attr((const char **) elem->attrs[i], "ip"); + mask = j_attr((const char **) elem->attrs[i], "mask"); + + if(ip == NULL) + continue; + + if(mask == NULL) + mask = "255.255.255.255"; + + access_deny(c2s->access, ip, mask); + } + } +} + +static int _c2s_router_connect(c2s_t c2s) { + log_write(c2s->log, LOG_NOTICE, "attempting connection to router at %s, port=%d", c2s->router_ip, c2s->router_port); + + c2s->fd = mio_connect(c2s->mio, c2s->router_port, c2s->router_ip, c2s_router_mio_callback, (void *) c2s); + if(c2s->fd < 0) { + if(errno == ECONNREFUSED) + c2s_lost_router = 1; + log_write(c2s->log, LOG_NOTICE, "connection attempt to router failed: %s (%d)", strerror(errno), errno); + return 1; + } + + c2s->router = sx_new(c2s->sx_env, c2s->fd, c2s_router_sx_callback, (void *) c2s); + sx_client_init(c2s->router, 0, NULL, NULL, NULL, "1.0"); + + return 0; +} + +static int _c2s_sx_sasl_callback(int cb, void *arg, void **res, sx_t s, void *cbarg) { + c2s_t c2s = (c2s_t) cbarg; + char *my_realm, *mech; + sx_sasl_creds_t creds; + static char buf[3072]; + char mechbuf[256]; + struct jid_st jid; + jid_static_buf jid_buf; + int i, r; + + /* init static jid */ + jid_static(&jid,&jid_buf); + + switch(cb) { + case sx_sasl_cb_GET_REALM: + + if(s->req_to == NULL) /* this shouldn't happen */ + my_realm = ""; + + else { + my_realm = xhash_get(c2s->realms, s->req_to); + if(my_realm == NULL) + my_realm = s->req_to; + } + + strncpy(buf, my_realm, 256); + *res = buf; + + log_debug(ZONE, "sx sasl callback: get realm: realm is '%s'", buf); + return sx_sasl_ret_OK; + break; + + case sx_sasl_cb_GET_PASS: + creds = (sx_sasl_creds_t) arg; + + log_debug(ZONE, "sx sasl callback: get pass (authnid=%s, realm=%s)", creds->authnid, creds->realm); + + if(c2s->ar->get_password && (c2s->ar->get_password)(c2s->ar, (char *)creds->authnid, (creds->realm != NULL) ? (char *)creds->realm: "", buf) == 0) { + *res = buf; + return sx_sasl_ret_OK; + } + + return sx_sasl_ret_FAIL; + + case sx_sasl_cb_CHECK_PASS: + creds = (sx_sasl_creds_t) arg; + + log_debug(ZONE, "sx sasl callback: check pass (authnid=%s, realm=%s)", creds->authnid, creds->realm); + + if(c2s->ar->check_password != NULL) { + if ((c2s->ar->check_password)(c2s->ar, (char *)creds->authnid, (creds->realm != NULL) ? (char *)creds->realm : "", (char *)creds->pass) == 0) + return sx_sasl_ret_OK; + else + return sx_sasl_ret_FAIL; + } + + if(c2s->ar->get_password != NULL) { + if ((c2s->ar->get_password)(c2s->ar, (char *)creds->authnid, (creds->realm != NULL) ? (char *)creds->realm : "", buf) != 0) + return sx_sasl_ret_FAIL; + + if (strcmp(creds->pass, buf)==0) + return sx_sasl_ret_OK; + } + + return sx_sasl_ret_FAIL; + break; + + case sx_sasl_cb_CHECK_AUTHZID: + creds = (sx_sasl_creds_t) arg; + + /* no authzid, we should build one */ + if(creds->authzid == NULL || creds->authzid[0] == '\0') { + snprintf(buf, 3072, "%s@%s", creds->authnid, s->req_to); + creds->authzid = (void *)buf; + } + + /* authzid must be a valid jid */ + jid.pc = c2s->pc; + if(jid_reset(&jid, creds->authzid, -1) == NULL) + return sx_sasl_ret_FAIL; + + /* and have domain == stream to addr */ + if(strcmp(jid.domain, s->req_to) != 0) + return sx_sasl_ret_FAIL; + + /* and have no resource */ + if(jid.resource[0] != '\0') + return sx_sasl_ret_FAIL; + + /* and exist ! */ + + if((c2s->ar->user_exists)(c2s->ar, (char *)creds->authnid, (char *)creds->realm)) + return sx_sasl_ret_OK; + + return sx_sasl_ret_FAIL; + + case sx_sasl_cb_GEN_AUTHZID: + /* generate a jid for SASL ANONYMOUS */ + jid.pc = c2s->pc; + jid_reset(&jid, s->req_to, -1); + + /* make resource a random string */ + jid_random_part(&jid, jid_NODE); + + strcpy(buf, jid_full(&jid)); + + *res = (void *)buf; + + return sx_sasl_ret_OK; + break; + + case sx_sasl_cb_CHECK_MECH: + mech = (char *)arg; + + i=0; + while(i sizeof(buf)) + return sx_sasl_ret_FAIL; + + /* Work out if our configuration will let us use this mechanism */ + if(config_get(c2s->config,buf) != NULL) + return sx_sasl_ret_OK; + else + return sx_sasl_ret_FAIL; + default: + break; + } + + return sx_sasl_ret_FAIL; +} +static void _c2s_time_checks(c2s_t c2s) { + sess_t sess; + time_t now; + union xhashv xhv; + + now = time(NULL); + + if(xhash_iter_first(c2s->sessions)) + do { + xhv.sess_val = &sess; + xhash_iter_get(c2s->sessions, NULL, xhv.val); + + if(c2s->io_check_idle > 0 && now > sess->last_activity + c2s->io_check_idle) { + log_write(c2s->log, LOG_NOTICE, "[%d] [%s, port=%d] timed out", sess->fd, sess->ip, sess->port); + + sx_error(sess->s, stream_err_HOST_GONE, "connection timed out"); + sx_close(sess->s); + + continue; + } + + if(c2s->io_check_keepalive > 0 && now > sess->last_activity + c2s->io_check_keepalive && sess->s->state >= state_STREAM) { + log_debug(ZONE, "sending keepalive for %d", sess->fd); + + sx_raw_write(sess->s, " ", 1); + } + + } while(xhash_iter_next(c2s->sessions)); +} + +int main(int argc, char **argv) +{ + c2s_t c2s; + char *config_file, *realm; + char id[1024]; + int i, sd_flags, optchar; + config_elem_t elem; + sess_t sess; + union xhashv xhv; +#ifdef POOL_DEBUG + time_t pool_time = 0; +#endif + +#ifdef HAVE_UMASK + umask((mode_t) 0027); +#endif + + srand(time(NULL)); + +#ifdef HAVE_WINSOCK2_H +/* get winsock running */ + { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + /* !!! tell user that we couldn't find a usable winsock dll */ + return 0; + } + } +#endif + + jabber_signal(SIGINT, _c2s_signal); + jabber_signal(SIGTERM, _c2s_signal); +#ifdef SIGHUP + jabber_signal(SIGHUP, _c2s_signal_hup); +#endif +#ifdef SIGPIPE + jabber_signal(SIGPIPE, SIG_IGN); +#endif + + c2s = (c2s_t) malloc(sizeof(struct c2s_st)); + memset(c2s, 0, sizeof(struct c2s_st)); + + /* load our config */ + c2s->config = config_new(); + + config_file = CONFIG_DIR "/c2s.xml"; + + /* cmdline parsing */ + while((optchar = getopt(argc, argv, "Dc:h?")) >= 0) + { + switch(optchar) + { + case 'c': + config_file = optarg; + break; + case 'D': +#ifdef DEBUG + set_debug_flag(1); +#else + printf("WARN: Debugging not enabled. Ignoring -D.\n"); +#endif + break; + case 'h': case '?': default: + fputs( + "c2s - jabberd client-to-server connector (" VERSION ")\n" + "Usage: c2s \n" + "Options are:\n" + " -c config file to use [default: " CONFIG_DIR "/c2s.xml]\n" +#ifdef DEBUG + " -D Show debug output\n" +#endif + , + stdout); + config_free(c2s->config); + free(c2s); + return 1; + } + } + + if(config_load(c2s->config, config_file) != 0) + { + fputs("c2s: couldn't load config, aborting\n", stderr); + config_free(c2s->config); + free(c2s); + return 2; + } + + _c2s_config_expand(c2s); + + c2s->log = log_new(c2s->log_type, c2s->log_ident, c2s->log_facility); + log_write(c2s->log, LOG_NOTICE, "starting up"); + + _c2s_pidfile(c2s); + + if(c2s->ar_module_name == NULL) + { + log_write(c2s->log, LOG_ERR, "no authreg module specified in config file"); + exit(1); + } + + if((c2s->ar = authreg_init(c2s, c2s->ar_module_name)) == NULL) + exit(1); + + c2s->pc = prep_cache_new(); + + c2s->sessions = xhash_new(1023); + + c2s->conn_rates = xhash_new(101); + + c2s->dead = jqueue_new(); + + c2s->dead_sess = jqueue_new(); + + c2s->sx_env = sx_env_new(); + +#ifdef HAVE_SSL + /* get the ssl context up and running */ + if(c2s->local_pemfile != NULL) { + c2s->sx_ssl = sx_env_plugin(c2s->sx_env, sx_ssl_init, c2s->local_pemfile, c2s->local_cachain, c2s->local_verify_mode); + if(c2s->sx_ssl == NULL) { + log_write(c2s->log, LOG_ERR, "failed to load local SSL pemfile, SSL will not be available to clients"); + c2s->local_pemfile = NULL; + } + } + + /* try and get something online, so at least we can encrypt to the router */ + if(c2s->sx_ssl == NULL && c2s->router_pemfile != NULL) { + c2s->sx_ssl = sx_env_plugin(c2s->sx_env, sx_ssl_init, c2s->router_pemfile, NULL); + if(c2s->sx_ssl == NULL) { + log_write(c2s->log, LOG_ERR, "failed to load router SSL pemfile, channel to router will not be SSL encrypted"); + c2s->router_pemfile = NULL; + } + } +#endif + + /* get sasl online */ + sd_flags = 0; + + c2s->sx_sasl = sx_env_plugin(c2s->sx_env, sx_sasl_init, "xmpp", sd_flags, _c2s_sx_sasl_callback, (void *) c2s, sd_flags); + if(c2s->sx_sasl == NULL) { + log_write(c2s->log, LOG_ERR, "failed to initialise SASL context, aborting"); + exit(1); + } + + sx_env_plugin(c2s->sx_env, bind_init); + + c2s->mio = mio_new(c2s->io_max_fds); + + /* realm mapping */ + c2s->realms = xhash_new(51); + + elem = config_get(c2s->config, "local.id"); + for(i = 0; i < elem->nvalues; i++) { + realm = j_attr((const char **) elem->attrs[i], "realm"); + + /* stringprep ids (domain names) so that they are in canonical form */ + strncpy(id, elem->values[i], 1024); + id[1023] = '\0'; +#ifdef HAVE_IDN + if (stringprep_nameprep(id, 1024) != 0) { + log_write(c2s->log, LOG_ERR, "cannot stringprep id %s, aborting", id); + exit(1); + } +#endif + xhash_put(c2s->realms, pstrdup(xhash_pool(c2s->realms), id), (realm != NULL) ? realm : pstrdup(xhash_pool(c2s->realms), id)); + + log_write(c2s->log, LOG_NOTICE, "[%s] configured; realm=%s", id, realm); + } + + c2s->sm_avail = xhash_new(51); + + c2s->retry_left = c2s->retry_init; + _c2s_router_connect(c2s); + + while(!c2s_shutdown) { + mio_run(c2s->mio, 5); + + if(c2s_logrotate) { + log_write(c2s->log, LOG_NOTICE, "reopening log ..."); + log_free(c2s->log); + c2s->log = log_new(c2s->log_type, c2s->log_ident, c2s->log_facility); + log_write(c2s->log, LOG_NOTICE, "log started"); + + c2s_logrotate = 0; + } + + if(c2s_lost_router) { + if(c2s->retry_left < 0) { + log_write(c2s->log, LOG_NOTICE, "attempting reconnect"); + sleep(c2s->retry_sleep); + c2s_lost_router = 0; + _c2s_router_connect(c2s); + } + + else if(c2s->retry_left == 0) { + c2s_shutdown = 1; + } + + else { + log_write(c2s->log, LOG_NOTICE, "attempting reconnect (%d left)", c2s->retry_left); + c2s->retry_left--; + sleep(c2s->retry_sleep); + c2s_lost_router = 0; + _c2s_router_connect(c2s); + } + } + + /* cleanup dead sess (before sx_t as sess->result uses sx_t nad cache) */ + while(jqueue_size(c2s->dead_sess) > 0) { + sess = (sess_t) jqueue_pull(c2s->dead_sess); + + /* free sess data */ + if(sess->ip != NULL) free(sess->ip); + if(sess->result != NULL) nad_free(sess->result); + if(sess->jid != NULL) jid_free(sess->jid); + + free(sess); + } + + /* cleanup dead sx_ts */ + while(jqueue_size(c2s->dead) > 0) + sx_free((sx_t) jqueue_pull(c2s->dead)); + + /* time checks */ + if(c2s->io_check_interval > 0 && time(NULL) >= c2s->next_check) { + log_debug(ZONE, "running time checks"); + + _c2s_time_checks(c2s); + + c2s->next_check = time(NULL) + c2s->io_check_interval; + log_debug(ZONE, "next time check at %d", c2s->next_check); + } + +#ifdef POOL_DEBUG + if(time(NULL) > pool_time + 60) { + pool_stat(1); + pool_time = time(NULL); + } +#endif + } + + log_write(c2s->log, LOG_NOTICE, "shutting down"); + + if(xhash_iter_first(c2s->sessions)) + do { + xhv.sess_val = &sess; + xhash_iter_get(c2s->sessions, NULL, xhv.val); + + if(sess->active) + sx_close(sess->s); + + } while(xhash_iter_next(c2s->sessions)); + + /* cleanup dead sess */ + while(jqueue_size(c2s->dead_sess) > 0) { + sess = (sess_t) jqueue_pull(c2s->dead_sess); + + /* free sess data */ + if(sess->ip != NULL) free(sess->ip); + if(sess->result != NULL) nad_free(sess->result); + if(sess->jid != NULL) jid_free(sess->jid); + + free(sess); + } + + while(jqueue_size(c2s->dead) > 0) + sx_free((sx_t) jqueue_pull(c2s->dead)); + + sx_free(c2s->router); + + sx_env_free(c2s->sx_env); + + mio_free(c2s->mio); + + xhash_free(c2s->sessions); + + prep_cache_free(c2s->pc); + + authreg_free(c2s->ar); + + xhash_free(c2s->conn_rates); + + xhash_free(c2s->sm_avail); + + xhash_free(c2s->realms); + + jqueue_free(c2s->dead); + + jqueue_free(c2s->dead_sess); + + access_free(c2s->access); + + log_free(c2s->log); + + config_free(c2s->config); + + free(c2s); + +#ifdef POOL_DEBUG + pool_stat(1); +#endif + +#ifdef HAVE_WINSOCK2_H + WSACleanup(); +#endif + + return 0; +} diff --git a/c2s/sm.c b/c2s/sm.c new file mode 100644 index 00000000..9f740268 --- /dev/null +++ b/c2s/sm.c @@ -0,0 +1,102 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "c2s.h" + +/** generate a new session request id */ +static void _sm_generate_id(sess_t sess, const char *type) { + char str[3094]; /* JID=3071 chars max + time = 12 chars max + type = 10 chars max + terminator = 3094 */ + + snprintf(str, 3094, "%s%d%s", type, (int) time(NULL), jid_full(sess->jid)); + str[3093] = '\0'; + + shahash_r(str, sess->sm_request); +} + +/** make a new action route */ +static nad_t _sm_build_route(sess_t sess, const char *action, const char *target, char *id) { + nad_t nad; + int ns, ans; + + nad = nad_new(sess->c2s->router->nad_cache); + + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, ns, "route", 0); + + nad_append_attr(nad, -1, "to", sess->jid->domain); + nad_append_attr(nad, -1, "from", sess->c2s->id); + + ans = nad_add_namespace(nad, uri_SESSION, "sc"); + nad_append_elem(nad, ans, "session", 1); + + if(sess->c2s_id[0] != '\0') + nad_append_attr(nad, ans, "c2s", sess->c2s_id); + if(sess->sm_id[0] != '\0') + nad_append_attr(nad, ans, "sm", sess->sm_id); + + nad_append_attr(nad, -1, "action", action); + + if(target != NULL) + nad_append_attr(nad, -1, "target", target); + if(id != NULL) + nad_append_attr(nad, -1, "id", id); + + log_debug(ZONE, "built new route nad for %s action %s target %s id %s", jid_full(sess->jid), action, target, id); + + return nad; +} + +void sm_start(sess_t sess) { + _sm_generate_id(sess, "start"); + + sx_nad_write(sess->c2s->router, _sm_build_route(sess, "start", jid_full(sess->jid), sess->sm_request)); +} + +void sm_end(sess_t sess) { + sx_nad_write(sess->c2s->router, _sm_build_route(sess, "end", NULL, NULL)); +} + +void sm_create(sess_t sess) { + _sm_generate_id(sess, "create"); + + sx_nad_write(sess->c2s->router, _sm_build_route(sess, "create", jid_user(sess->jid), sess->sm_request)); +} + +void sm_delete(sess_t sess) { + sx_nad_write(sess->c2s->router, _sm_build_route(sess, "delete", jid_user(sess->jid), NULL)); +} + +void sm_packet(sess_t sess, nad_t nad) { + int ns; + + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_wrap_elem(nad, 0, ns, "route"); + + nad_set_attr(nad, 0, -1, "to", sess->jid->domain, 0); + nad_set_attr(nad, 0, -1, "from", sess->c2s->id, 0); + + ns = nad_append_namespace(nad, 1, uri_SESSION, "sc"); + + nad_set_attr(nad, 1, ns, "c2s", sess->c2s_id, 0); + if(sess->c2s_id[0] != '\0') + nad_set_attr(nad, 1, ns, "sm", sess->sm_id, 0); + + sx_nad_write(sess->c2s->router, nad); +} diff --git a/config.rpath b/config.rpath new file mode 100755 index 00000000..5ead7586 --- /dev/null +++ b/config.rpath @@ -0,0 +1,513 @@ +#! /bin/sh +# Output a system dependent set of variables, describing how to set the +# run time search path of shared libraries in an executable. +# +# Copyright 1996-2002 Free Software Foundation, Inc. +# Taken from GNU libtool, 2001 +# Originally by Gordon Matzigkeit , 1996 +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. +# +# The first argument passed to this file is the canonical host specification, +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld +# should be set by the caller. +# +# The set of defined variables is at the end of this script. + +# All known linkers require a `.a' archive for static linking (except M$VC, +# which needs '.lib'). +libext=a +shlibext= + +host="$1" +host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` +host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` + +wl= +if test "$GCC" = yes; then + wl='-Wl,' +else + case "$host_os" in + aix3* | aix4* | aix5*) + wl='-Wl,' + ;; + hpux9* | hpux10* | hpux11*) + wl='-Wl,' + ;; + irix5* | irix6*) + wl='-Wl,' + ;; + linux*) + echo '__INTEL_COMPILER' > conftest.$ac_ext + if $CC -E conftest.$ac_ext >/dev/null | grep __INTEL_COMPILER >/dev/null + then + : + else + # Intel icc + wl='-Qoption,ld,' + fi + ;; + osf3* | osf4* | osf5*) + wl='-Wl,' + ;; + solaris*) + wl='-Wl,' + ;; + sunos4*) + wl='-Qoption ld ' + ;; + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + if test "x$host_vendor" = xsni; then + wl='-LD' + else + wl='-Wl,' + fi + ;; + esac +fi + +hardcode_libdir_flag_spec= +hardcode_libdir_separator= +hardcode_direct=no +hardcode_minus_L=no + +case "$host_os" in + cygwin* | mingw* | pw32*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + openbsd*) + with_gnu_ld=no + ;; +esac + +ld_shlibs=yes +if test "$with_gnu_ld" = yes; then + case "$host_os" in + aix3* | aix4* | aix5*) + # On AIX, the GNU linker is very broken + ld_shlibs=no + ;; + amigaos*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + # Samuel A. Falvo II reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we can use + # them. + ld_shlibs=no + ;; + beos*) + if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + cygwin* | mingw* | pw32*) + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + ;; + solaris* | sysv5*) + if $LD -v 2>&1 | egrep 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + elif $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + sunos4*) + hardcode_direct=yes + ;; + *) + if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + esac + if test "$ld_shlibs" = yes; then + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + fi +else + case "$host_os" in + aix3*) + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test "$GCC" = yes; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + aix4* | aix5*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + else + aix_use_runtimelinking=no + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix5*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + esac + fi + hardcode_direct=yes + hardcode_libdir_separator=':' + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + hardcode_direct=yes + else + # We have old collect2 + hardcode_direct=unsupported + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + esac + fi + if test "$aix_use_runtimelinking" = yes; then + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:/usr/lib:/lib' + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' + else + hardcode_libdir_flag_spec='${wl}-bnolibpath ${wl}-blibpath:$libdir:/usr/lib:/lib' + fi + fi + ;; + amigaos*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + # see comment about different semantics on the GNU ld section + ld_shlibs=no + ;; + cygwin* | mingw* | pw32*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec=' ' + libext=lib + ;; + darwin* | rhapsody*) + hardcode_direct=yes + ;; + freebsd1*) + ld_shlibs=no + ;; + freebsd2.2*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + freebsd2*) + hardcode_direct=yes + hardcode_minus_L=yes + ;; + freebsd*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + hpux9* | hpux10* | hpux11*) + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + hardcode_minus_L=yes # Not in the search PATH, but as the default + # location of the library. + ;; + irix5* | irix6*) + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + netbsd*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + newsos6) + hardcode_direct=yes + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + openbsd*) + hardcode_direct=yes + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + else + case "$host_os" in + openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) + hardcode_libdir_flag_spec='-R$libdir' + ;; + *) + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + ;; + esac + fi + ;; + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + osf3*) + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + osf4* | osf5*) + if test "$GCC" = yes; then + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + else + # Both cc and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + hardcode_libdir_separator=: + ;; + sco3.2v5*) + ;; + solaris*) + hardcode_libdir_flag_spec='-R$libdir' + ;; + sunos4*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + ;; + sysv4) + if test "x$host_vendor" = xsno; then + hardcode_direct=yes # is this really true??? + else + hardcode_direct=no # Motorola manual says yes, but my tests say they lie + fi + ;; + sysv4.3*) + ;; + sysv5*) + hardcode_libdir_flag_spec= + ;; + uts4*) + hardcode_libdir_flag_spec='-L$libdir' + ;; + dgux*) + hardcode_libdir_flag_spec='-L$libdir' + ;; + sysv4*MP*) + if test -d /usr/nec; then + ld_shlibs=yes + fi + ;; + sysv4.2uw2*) + hardcode_direct=yes + hardcode_minus_L=no + ;; + sysv5uw7* | unixware7*) + ;; + *) + ld_shlibs=no + ;; + esac +fi + +# Check dynamic linker characteristics +libname_spec='lib$name' +sys_lib_dlsearch_path_spec="/lib /usr/lib" +sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +case "$host_os" in + aix3*) + shlibext=so + ;; + aix4* | aix5*) + shlibext=so + ;; + amigaos*) + shlibext=ixlibrary + ;; + beos*) + shlibext=so + ;; + bsdi4*) + shlibext=so + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + ;; + cygwin* | mingw* | pw32*) + case $GCC,$host_os in + yes,cygwin*) + shlibext=dll.a + ;; + yes,mingw*) + shlibext=dll + sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | sed -e "s/^libraries://" -e "s/;/ /g"` + ;; + yes,pw32*) + shlibext=dll + ;; + *) + shlibext=dll + ;; + esac + ;; + darwin* | rhapsody*) + shlibext=dylib + ;; + freebsd1*) + ;; + freebsd*) + shlibext=so + ;; + gnu*) + shlibext=so + ;; + hpux9* | hpux10* | hpux11*) + shlibext=sl + ;; + irix5* | irix6*) + shlibext=so + case "$host_os" in + irix5*) + libsuff= shlibsuff= + ;; + *) + case $LD in + *-32|*"-32 ") libsuff= shlibsuff= ;; + *-n32|*"-n32 ") libsuff=32 shlibsuff=N32 ;; + *-64|*"-64 ") libsuff=64 shlibsuff=64 ;; + *) libsuff= shlibsuff= ;; + esac + ;; + esac + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + ;; + linux-gnuoldld* | linux-gnuaout* | linux-gnucoff*) + ;; + linux-gnu*) + shlibext=so + ;; + netbsd*) + shlibext=so + ;; + newsos6) + shlibext=so + ;; + openbsd*) + shlibext=so + ;; + os2*) + libname_spec='$name' + shlibext=dll + ;; + osf3* | osf4* | osf5*) + shlibext=so + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + sco3.2v5*) + shlibext=so + ;; + solaris*) + shlibext=so + ;; + sunos4*) + shlibext=so + ;; + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + shlibext=so + case "$host_vendor" in + motorola) + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + uts4*) + shlibext=so + ;; + dgux*) + shlibext=so + ;; + sysv4*MP*) + if test -d /usr/nec; then + shlibext=so + fi + ;; +esac + +sed_quote_subst='s/\(["`$\\]\)/\\\1/g' +escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"` +escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` +escaped_sys_lib_search_path_spec=`echo "X$sys_lib_search_path_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` +escaped_sys_lib_dlsearch_path_spec=`echo "X$sys_lib_dlsearch_path_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` + +sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' < +#endif +#ifdef HAVE_WINSOCK2_H +# include +#endif +#ifdef HAVE_NETINET_IN_H +# include +#endif]) + + +dnl +dnl quirky functions +dnl + +AC_CHECK_FUNC(connect, ,[AC_CHECK_LIB(socket, connect)]) +AC_CHECK_LIB(ws2_32, _head_libws2_32_a) +AC_CHECK_FUNC(gethostbyname, ,[AC_CHECK_LIB(resolv, gethostbyname)]) +AC_CHECK_FUNC(gethostbyname, ,[AC_CHECK_LIB(nsl, gethostbyname)]) + +if test "x$ac_cv_lib_nsl_gethostbyname" != "xyes" && test "x$ac_cv_func_gethostbyname" != "xyes" ; then + AC_CHECK_FUNC(gethostbyname, , [AC_CHECK_LIB(socket, gethostbyname)]) +fi + +if test "$ac_cv_lib_nsl_gethostbyname" = "$ac_cv_func_gethostbyname" ; then + AC_MSG_CHECKING([if we can include libnsl + libsocket]) + LIBS="-lnsl -lsocket $LIBS" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[(void) gethostbyname]])],[my_ac_link_result=yes],[my_ac_link_result=no ]) + if test "$my_ac_link_result" = "no" ; then + AC_MSG_RESULT([failure]) + AC_MSG_ERROR([unable to use gethostbyname()]) + else + AC_MSG_RESULT([success]) + fi +fi + +dnl res_query has been seen in libc, libbind and libresolv +if test "x-$ac_cv_header_resolv_h" = "x-yes" ; then + AC_CHECK_FUNCS(res_query) + if test "x-$ac_cv_func_res_query" = "x-yes" ; then + have_res_query=yes + else + AC_CHECK_LIB(resolv, res_query) + if test "x-$ac_cv_lib_resolv_res_query" = "x-yes" ; then + have_res_query=yes + else + AC_CHECK_LIB(bind, res_query) + if test "x-$ac_cv_lib_bind_res_query" = "x-yes" ; then + have_res_query=yes + else + dnl some glibcs have res_query as a macro, so work around it + AC_MSG_CHECKING([for res_query in -lresolv (alternate version)]) + save_libs="$LIBS" + LIBS="-lresolv $LIBS" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[res_query(0,0,0,0,0)]])], + [AC_MSG_RESULT(yes) + have_res_query=yes], + [AC_MSG_RESULT(no) + LIBS="$save_libs"]) + fi + fi + fi +fi + +dnl windows calls it DnsQuery +if test "x-$ac_cv_header_windns_h" = "x-yes" ; then + AC_MSG_CHECKING([for DnsQuery in -ldnsapi]) + save_libs="$LIBS" + LIBS="-ldnsapi $LIBS" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include + #include ]], + [[DnsQuery(0,0,0,0,0,0)]])], + [AC_MSG_RESULT(yes) + have_dnsquery=yes], + [AC_MSG_RESULT(no) + LIBS="$save_libs"]) +fi + +if test "x-$have_res_query" = "x-yes" ; then + AC_DEFINE(HAVE_RES_QUERY,1,[Define to 1 if you have the 'res_query' function.]) +elif test "x-$have_dnsquery" = "x-yes" ; then + AC_DEFINE(HAVE_DNSQUERY,1,[Define to 1 if you have the 'DnsQuery' function.]) +else + AC_MSG_ERROR([no DNS resolver interface (res_query or DnsQuery) found]) +fi + +dnl inet_ntop/inet_pton have been seen in -lnsl, and sometimes not at all +AC_CHECK_FUNC(inet_ntop, ,[AC_CHECK_LIB(nsl, inet_ntop)]) +unset ac_cv_func_inet_ntop +AC_CHECK_FUNCS(inet_ntop inet_pton) + +dnl some glibcs have broken sockaddr_storage members +if test "x-$ac_cv_type_struct_sockaddr_storage" = "x-yes" ; then + AC_MSG_CHECKING(for broken __ss_family member in struct sockaddr_storage) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM(_IP6_INCLUDES, + [[do { + struct sockaddr_storage s; + s.__ss_family = 0; + } while(0)]])], + [AC_MSG_RESULT(yes) + AC_DEFINE(ss_family, __ss_family, + [Define to '__ss_family' if 'struct sockaddr_storage' defines '__ss_family' instead of 'ss_family'.])], + AC_MSG_RESULT(no)) + + AC_MSG_CHECKING(for broken __ss_len member in struct sockaddr_storage) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM(_IP6_INCLUDES, + [[do { + struct sockaddr_storage s; + s.__ss_len = 0; + } while(0)]])], + [AC_MSG_RESULT(yes) + AC_DEFINE(ss_len, __ss_len, + [Define to '__ss_len' if 'struct sockaddr_storage' defines '__ss_len' instead of 'ss_len'.])], + AC_MSG_RESULT(no)) +fi + + +dnl syslog +if test "x-$ac_cv_header_syslog_h" = "x-yes" ; then + AC_CHECK_FUNCS(syslog) +fi + +if test "x-$ac_cv_header_windows_h" = "x-yes" ; then + AC_MSG_CHECKING(for ReportEvent) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[ReportEvent(0,0,0,0,0,0,0,0,0)]])], + [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_REPORTEVENT,1,[Define to 1 if you have the 'ReportEvent' function.])], + AC_MSG_RESULT(no)) +fi + + +dnl snprintf/vsnprintf don't exist everywhere. additionally, we require +dnl them to gracefully accept NULLs, which is non-standard +AC_CHECK_FUNCS(snprintf vsnprintf) +if test "x-$ac_cv_func_snprintf" = "x-yes" ; then + AC_MSG_CHECKING([if snprintf can handle NULL arguments]) + AC_RUN_IFELSE([AC_LANG_SOURCE([[#include + #include + segv() { exit(1); } + main() { char b[10]; signal(SIGSEGV,segv); snprintf(b,10,"%s",NULL); exit(0); }]])], + AC_MSG_RESULT(yes), + [AC_MSG_RESULT(no) + AC_DEFINE(HAVE_BROKEN_SNPRINTF,1,[Define to 1 if 'snprintf' cannot handle NULL arguments.])]) +fi +if test "x-$ac_cv_func_vsnprintf" = "x-yes" ; then + AC_MSG_CHECKING([if vsnprintf can handle NULL arguments]) + AC_RUN_IFELSE([AC_LANG_SOURCE([[#include + #include + #include + segv() { exit(1); } + expand(char *f,...) { va_list ap; char b[10]; va_start(ap,f); vsnprintf(b,10,f,ap); va_end(ap); } + main() { char b[10]; signal(SIGSEGV,segv); expand("%s", NULL); exit(0); }]])], + AC_MSG_RESULT(yes), + [AC_MSG_RESULT(no) + AC_DEFINE(HAVE_BROKEN_VSNPRINTF,1,[Define to 1 if 'vsnprintf' cannot handle NULL arguments.])]) +fi + + +dnl +dnl external packages +dnl + +dnl find libidn >= 0.3.0 +AC_ARG_ENABLE(idn, AC_HELP_STRING([--enable-idn], [enable IDN support (yes)]), want_idn=$enableval, want_idn=yes) +if test "x-$want_idn" = "x-yes" ; then + AC_CHECK_HEADERS(stringprep.h) + if test "x-$ac_cv_header_stringprep_h" = "x-yes" ; then + AC_CHECK_LIB(idn, stringprep_check_version) + fi + if test "x-$ac_cv_lib_idn_stringprep_check_version" = "x-yes" ; then + AC_MSG_CHECKING(for Libidn version >= 0.3.0) + AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[return !(stringprep_check_version("0.3.0"))]])], + [AC_MSG_RESULT(yes) + have_idn=yes], + AC_MSG_RESULT(no)) + fi + if test "x-$have_idn" = "x-" ; then + AC_MSG_ERROR([Libidn >= 0.3.0 not found]) + fi + + AC_DEFINE(HAVE_IDN,1,[Define to 1 if Libidn is available.]) +fi + + +AC_CHECK_LIB(sasl2,sasl_client_init) + +dnl find openssl >= 0.9.6b +AC_ARG_ENABLE(ssl, AC_HELP_STRING([--enable-ssl], [enable SSL/TLS support (yes)]), want_ssl=$enableval, want_ssl=yes) +if test "x-$want_ssl" = "x-yes" ; then + AC_CHECK_HEADERS(openssl/crypto.h) + if test "x-$ac_cv_header_openssl_crypto_h" = "x-yes" ; then + AC_CHECK_LIB(crypto, CRYPTO_lock) + fi + if test "x-$ac_cv_lib_crypto_CRYPTO_lock" = "x-yes" ; then + AC_CHECK_HEADERS(openssl/ssl.h) + fi + if test "x-$ac_cv_header_openssl_ssl_h" = "x-yes" ; then + AC_CHECK_LIB(ssl, SSL_connect) + fi + if test "x-$ac_cv_lib_ssl_SSL_connect" = "x-yes" ; then + AC_MSG_CHECKING(for OpenSSL version >= 0.9.6b) + AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[return !(SSLeay() >= 0x000906020L)]])], + [AC_MSG_RESULT(yes) + have_openssl=yes], + AC_MSG_RESULT(no)) + fi + if test "x-$have_openssl" = "x-" ; then + AC_MSG_ERROR([OpenSSL >= 0.9.6b not found]) + fi + + AC_DEFINE(HAVE_SSL,1,[Define to 1 if OpenSSL is available.]) +fi + + +dnl +dnl optional libs +dnl + +dnl mysql +AC_ARG_ENABLE(mysql, AC_HELP_STRING([--enable-mysql], [enable MySQL auth/reg/storage support (yes)]), + want_mysql=$enableval, want_mysql=yes) +if test "x-$want_mysql" = "x-yes" ; then + AC_CHECK_HEADERS(mysql.h) + if test "x-$ac_cv_header_mysql_h" != "x-yes" ; then + for incpath in /usr/include/mysql /usr/local/include/mysql; do + if test "x-$ac_cv_header_mysql_h" != "x-yes" ; then + AC_MSG_CHECKING([for mysql.h in $incpath]) + save_cppflags="$CPPFLAGS" + CPPFLAGS="-I$incpath $CPPFLAGS" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]])], + [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_MYSQL_H,,[Define if you have mysql.h]) + ac_cv_header_mysql_h=yes], + AC_MSG_RESULT(no)) + if test "x-$ac_cv_header_mysql_h" != "x-yes" ; then + CPPFLAGS="$save_cppflags" + fi + fi + done + fi + if test "x-$ac_cv_header_mysql_h" = "x-yes" ; then + AC_CHECK_LIB(mysqlclient, mysql_init) + fi + if test "x-$ac_cv_lib_mysqlclient_mysql_init" != "x-yes" ; then + AC_MSG_ERROR([MySQL client libraries not found]) + else + AC_DEFINE(STORAGE_MYSQL,1,[Define to 1 if you want to use MySQL for auth/reg/storage.]) + fi +fi + + +dnl postgresql +AC_ARG_ENABLE(pgsql, AC_HELP_STRING([--enable-pgsql], [enable PostgreSQL auth/reg/storage support (no)]), + want_pgsql=$enableval, want_pgsql=no) +if test "x-$want_pgsql" = "x-yes" ; then + AC_CHECK_HEADERS(libpq-fe.h) + if test "x-$ac_cv_header_libpq_fe_h" != "x-yes" ; then + for incpath in /usr/include/postgresql /usr/local/include/postgresql; do + if test "x-$ac_cv_header_libpq_fe_h" != "x-yes" ; then + AC_MSG_CHECKING([for libpq-fe.h in $incpath]) + save_cppflags="$CPPFLAGS" + CPPFLAGS="-I$incpath $CPPFLAGS" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]])], + [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_LIBPQ_FE_H,,[Define if you have libpq_fe.h]) + ac_cv_header_libpq_fe_h=yes], + AC_MSG_RESULT(no)) + if test "x-$ac_cv_header_libpq_fe_h" != "x-yes" ; then + CPPFLAGS="$save_cppflags" + fi + fi + done + fi + if test "x-$ac_cv_header_libpq_fe_h" = "x-yes" ; then + AC_CHECK_LIB(pq, PQsetdbLogin) + fi + if test "x-$ac_cv_lib_pq_PQsetdbLogin" != "x-yes" ; then + AC_MSG_ERROR([PostgreSQL client libraries not found]) + else + AC_DEFINE(STORAGE_PGSQL,1,[Define to 1 if you want to use PostgreSQL for auth/reg/storage.]) + fi +fi + + +dnl SQLite 3 +AC_ARG_ENABLE(sqlite, AC_HELP_STRING([--enable-sqlite], [enable SQLite 3 storage support (no)]), + want_sqlite=$enableval, want_sqlite=no) +if test "x-$want_sqlite" = "x-yes" ; then + AC_CHECK_HEADERS(sqlite3.h) + if test "x-$ac_cv_header_sqlite3_h" = "x-yes" ; then + AC_CHECK_LIB(sqlite3, sqlite3_open) + fi + if test "x-$ac_cv_lib_sqlite3_sqlite3_open" != "x-yes" ; then + AC_MSG_ERROR([SQLite 3 library not found]) + else + AC_DEFINE(STORAGE_SQLITE,1,[Define to 1 if you want to use SQLite 3 for storage.]) + fi +fi + + +dnl berkeley db +AC_ARG_ENABLE(db, AC_HELP_STRING([--enable-db], [enable Berkeley DB auth/reg/storage support (no)]), + want_db=$enableval, want_db=no) +if test "x-$want_db" = "x-yes" ; then + AC_CHECK_HEADERS(db.h) + if test "x-$ac_cv_header_db_h" = "x-yes" ; then + for lib in db-4.3 db-4.2 db-4.1 db-4 db4 db41 db ; do + if test "x-$have_db_version" != "x-yes" ; then + AC_MSG_CHECKING([for db_create in -l$lib]) + save_libs="$LIBS" + LIBS="-l$lib $LIBS" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[db_create(0,0,0)]])], + [AC_MSG_RESULT(yes) + AC_MSG_CHECKING(for Berkeley DB version >= 4.1.25) + AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[do { + int major, minor, patch; + db_version(&major, &minor, &patch); + if(major < 4 || + (major == 4 && minor < 1) || + (major == 4 && minor == 1 && patch < 24)) + return 1; + } while(0)]])], + [AC_MSG_RESULT(yes) + have_db_version=yes], + AC_MSG_RESULT(no))], + AC_MSG_RESULT(no)) + if test "x-$have_db_version" = "x-" ; then + LIBS="$save_libs" + fi + fi + done + fi + if test "x-$have_db_version" != "x-yes" ; then + AC_MSG_ERROR([Berkeley DB >= 4.1.24 not found]) + else + AC_DEFINE(STORAGE_DB,1,[Define to 1 if you want to use Berkeley DB for auth/reg/storage.]) + fi +fi + + +dnl openldap +AC_ARG_ENABLE(ldap, AC_HELP_STRING([--enable-ldap], [enable OpenLDAP auth/reg support (no)]), + want_ldap=$enableval, want_ldap=no) +if test "x-$want_ldap" = "x-yes" ; then + AC_CHECK_HEADERS(lber.h ldap.h) + if test "x-$ac_cv_header_ldap_h" = "x-yes" -a "x-$ac_cv_header_lber_h" = "x-yes" ; then + AC_CHECK_LIB(lber, ber_alloc) + AC_CHECK_LIB(ldap, ldap_init) + fi + if test "x-$ac_cv_lib_lber_ber_alloc" = "x-yes" -a "x-$ac_cv_lib_ldap_ldap_init" = "x-yes" ; then + AC_MSG_CHECKING(for OpenLDAP version >= 2.1.0) + AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include + #include ]], + [[do { + LDAPAPIInfo info; + info.ldapai_info_version = LDAP_API_INFO_VERSION; + ldap_get_option(0, LDAP_OPT_API_INFO, &info); + if(info.ldapai_vendor_version != LDAP_VENDOR_VERSION || LDAP_VENDOR_VERSION < 2004) + return 1; + } while(0)]])], + [AC_MSG_RESULT(yes) + have_ldap_version=yes], + AC_MSG_RESULT(no)) + fi + if test "x-$want_ldap" = "x-yes" -a "x-$have_ldap_version" = "x-" ; then + AC_MSG_ERROR([OpenLDAP client libraries >= 2.1.0 not found]) + else + AC_DEFINE(STORAGE_LDAP,1,[Define to 1 if you want to use OpenLDAP for auth/reg.]) + fi +fi + + +dnl pam +AC_ARG_ENABLE(pam, AC_HELP_STRING([--enable-pam], [enable PAM auth/reg support (no)]), + want_pam=$enableval, want_pam=no) +if test "x-$want_pam" = "x-yes" ; then + AC_CHECK_HEADERS(security/pam_appl.h) + if test "x-$ac_cv_header_security_pam_appl_h" = "x-yes" ; then + AC_CHECK_LIB(pam, pam_start) + fi + if test "x-$ac_cv_lib_pam_pam_start" != "x-yes" ; then + AC_MSG_ERROR([PAM application libraries not found]) + else + AC_DEFINE(STORAGE_PAM,1,[Define to 1 if you want to use PAM for auth/reg.]) + fi +fi + + +dnl pipe (not really an external package, but does need some checks) +AC_ARG_ENABLE(pipe, AC_HELP_STRING([--enable-pipe], [enable pipe auth/reg support (no)]), + want_pipe=$enableval, want_pipe=no) +if test "x-$want_pipe" = "x-yes" ; then + AC_CHECK_HEADERS(sys/wait.h) + AC_FUNC_FORK + AC_CHECK_FUNCS(pipe wait) + if test "x-$ac_cv_header_sys_wait_h" != "x-yes" -o \ + "x-$ac_cv_func_fork" != "x-yes" -o \ + "x-$ac_cv_func_pipe" != "x-yes" -o \ + "x-$ac_cv_func_wait" != "x-yes" ; then + AC_MSG_ERROR([Pipe auth/reg requirements (sys/wait.h, fork(), pipe(), wait()) not found]) + else + AC_DEFINE(STORAGE_PIPE,1,[Define to 1 if you want to use pipes for auth/reg.]) + fi +fi + + +dnl anon +AC_ARG_ENABLE(anon, AC_HELP_STRING([--enable-anon], [enable anonymous auth/reg support (no)]), + want_anon=$enableval, want_anon=no) +if test "x-$want_anon" = "x-yes" ; then + AC_DEFINE(STORAGE_ANON,1,[Define to 1 if you want anonymous auth.]) +fi + + +dnl filesystem storage +AC_ARG_ENABLE(fs, AC_HELP_STRING([--enable-fs], [enable filesystem storage support (no)]), + want_fs=$enableval, want_fs=no) +if test "x-$want_fs" = "x-yes" ; then + AC_DEFINE(STORAGE_FS,1,[Define to 1 if you want to use the filesystem for storage.]) +fi + +dnl Oracle +AC_ARG_WITH(oracle-home, + [ --with-oracle-home=DIR the Oracle home directory, for includes and libs. ], + [ ac_oracle_home="$withval" ]) + +AC_ARG_ENABLE(oracle, AC_HELP_STRING([--enable-oracle], [enable Oracle auth/reg/storage support (no)]), + want_oracle=$enableval, want_oracle=no) +if test "x-$want_oracle" = "x-yes" ; then + AC_CHECK_HEADERS(oci.h) + if test "x-$ac_cv_header_oci_h" != "x-yes" ; then + if test -n $ac_oracle_home ; then + AC_MSG_CHECKING([for oci.h in $ac_oracle_home]) + save_cppflags="$CPPFLAGS" + CPPFLAGS="-I$ac_oracle_home/rdbms/demo -I$ac_oracle_home/rdbms/public $CPPFLAGS" + save_libs="$LIBS" + LIBS="-L$ac_oracle_home/lib $LIBS" + save_ldflags="$LDFLAGS" + LDFLAGS="-Wl,-rpath,$ac_oracle_home/lib $LDFLAGS" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]])], + [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_OCI_H,,[Define if you have oci.h]) + ac_cv_header_oci_h=yes], + AC_MSG_RESULT(no)) + if test "x-$ac_cv_header_oci_h" != "x-yes" ; then + CPPFLAGS="$save_cppflags" + LIBS="$save_libs" + LDFLAGS="$save_ldflags" + fi + fi + fi + if test "x-$ac_cv_header_oci_h" = "x-yes" ; then + AC_CHECK_LIB(clntsh, OCIInitialize) + fi + if test "x-$ac_cv_lib_clntsh_OCIInitialize" != "x-yes" ; then + AC_MSG_ERROR([Oracle client libraries not found]) + else + AC_DEFINE(STORAGE_ORACLE,1,[Define to 1 if you want to use Oracle for auth/reg/storage.]) + fi +fi + + + +dnl +dnl generic system types +dnl +AC_CREATE_STDINT_H(ac-stdint.h) + + + + +dnl +dnl typedefs, structs, compiler quirks +dnl +AC_TYPE_MODE_T +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_STRUCT_TM +AC_STRUCT_TIMEZONE + + +dnl +dnl ipv6 stuff +dnl +AC_DEFUN([_IP6_INCLUDES],[[ +#include "ac-stdint.h" +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#ifdef HAVE_NETINET_IN_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif +]]) + +dnl these types are missing in places +AC_CHECK_TYPES([in_port_t, sa_family_t, struct sockaddr_storage, struct sockaddr_in6, struct in6_addr],,, _IP6_INCLUDES) + + +dnl +dnl mio backend checks +dnl +AC_ARG_ENABLE(mio, AC_HELP_STRING([--enable-mio=BACKEND], [use BACKEND to drive MIO]), + mio_check=$enableval, mio_check='poll select') + +mio_backend='' +for backend in $mio_check ; do + if test "x-$mio_backend" = "x-" ; then + case x-$backend in + + x-poll) + AC_CHECK_HEADERS(poll.h) + if test "x-$ac_cv_header_poll_h" = "x-yes" ; then + AC_CHECK_FUNCS(poll,[ + mio_backend='poll' + AC_DEFINE(MIO_POLL,1,[Define to 1 if you want to use 'poll' for non-blocking I/O.])]) + fi + ;; + + x-select) + AC_CHECK_HEADERS(sys/select.h) + if test "x-$ac_cv_header_sys_select_h" = "x-yes" ; then + AC_CHECK_FUNCS(select, have_select=yes) + fi + + if test "x-$have_select" != "x-yes" -a "x-$ac_cv_header_winsock2_h" = "x-yes" ; then + AC_MSG_CHECKING([for select in ws2_32]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[select(0,0,0,0,0)]])], + [AC_MSG_RESULT(yes) + have_select=yes], + AC_MSG_RESULT(no)) + fi + + if test "x-$have_select" = "x-yes" ; then + mio_backend='select' + AC_DEFINE(MIO_SELECT,1,[Define to 1 if you want to use 'select' for non-blocking I/O.]) + fi + ;; + esac + fi +done + +if test "x-$mio_backend" = "x-" ; then + AC_MSG_ERROR([no MIO backend available out of: $backend]) +fi + + +dnl functions we need +AC_FUNC_MALLOC +AC_FUNC_MEMCMP +AC_FUNC_MKTIME +AC_FUNC_REALLOC +AC_FUNC_STAT +AC_FUNC_VPRINTF +AC_CHECK_FUNCS( \ + close \ + dup2 \ + fcntl \ + inet_aton \ + ioctl \ + isascii \ + _findfirst \ + getopt \ + gettimeofday \ + getpid \ + _getpid \ + memchr \ + memmove \ + memset \ + mkdir \ + _mkdir \ + modf \ + sleep \ + Sleep \ + strcasecmp \ + stricmp \ + strchr \ + strdup \ + strerror \ + strncasecmp \ + strnicmp \ + strstr \ + tzset \ + uname \ + getpagesize ) + +dnl windows has different names for a few basic things +if test "x-$ac_cv_func_getpid" != "x-yes" -a "x-$ac_cv_func__getpid" = "x-yes" ; then + AC_DEFINE(getpid,_getpid,[Define to a function than can provide getpid(2) functionality.]) +fi + +if test "x-$ac_cv_func_sleep" != "x-yes" -a "x-$ac_cv_func_Sleep" = "x-yes" ; then + AC_DEFINE(sleep,Sleep,[Define to a function than can provide sleep(2) functionality.]) +fi + +if test "x-$ac_cv_func_strcasecmp" != "x-yes" -a "x-$ac_cv_func_stricmp" = "x-yes" ; then + AC_DEFINE(strcasecmp,stricmp,[Define to a function than can provide strcasecmp(3) functionality.]) +fi + +if test "x-$ac_cv_func_strncasecmp" != "x-yes" -a "x-$ac_cv_func_strnicmp" = "x-yes" ; then + AC_DEFINE(strncasecmp,strnicmp,[Define to a function than can provide strncasecmp(3) functionality.]) +fi + +dnl winsock substitutions +if test "x-$ac_cv_func_close" != "x-yes" ; then + AC_MSG_CHECKING(for closesocket) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[closesocket(0)]])], + [AC_MSG_RESULT(yes) + AC_DEFINE(close,closesocket,[Define to a function than can provide close(2) functionality.])], + AC_DEFINE(HAVE_CLOSE,1,[Define to 1 if you have the 'close' function.]) + AC_MSG_RESULT(no)) +fi + +if test "x-$ac_cv_func_ioctl" != "x-yes" ; then + AC_MSG_CHECKING(for ioctlsocket) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[ioctlsocket(0,0,0)]])], + [AC_MSG_RESULT(yes) + AC_DEFINE(ioctl,ioctlsocket,[Define to a function than can provide ioctl(2) functionality.])], + AC_DEFINE(HAVE_IOCTL,1,[Define to 1 if you have the 'ioctl' function.]) + AC_MSG_RESULT(no)) +fi + + +dnl +dnl debugging +dnl +AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], [enable debug messages]), + want_debug=$enableval, want_debug=no) +if test "x-$want_debug" = "x-yes" ; then + AC_DEFINE(DEBUG,1,[Define to 1 if you want to get debug output with -D.]) + AC_DEFINE(SX_DEBUG,1,[Define to 1 if you want to get SX debug output with -D.]) +fi + +AC_ARG_ENABLE(nad_debug, AC_HELP_STRING([--enable-nad-debug], [enable NAD pointer tracking]), + want_nad_debug=$enableval, want_nad_debug=no) +if test "x-$want_nad_debug" = "x-yes" ; then + AC_DEFINE(NAD_DEBUG,1,[Define to 1 if you want to enable NAD pointer tracking.]) +fi + +AC_ARG_ENABLE(pool_debug, AC_HELP_STRING([--enable-pool-debug], [enable memory pool statistics]), + want_pool_debug=$enableval, want_pool_debug=no) +if test "x-$want_pool_debug" = "x-yes" ; then + AC_DEFINE(POOL_DEBUG,1,[Define to 1 if you want to enable memory pool statistics.]) +fi + + +dnl +dnl finishing up +dnl + +dnl put our config in its own subdir +sysconfdir="$sysconfdir/jabberd" + +dnl done! +AC_OUTPUT(Makefile \ + etc/Makefile \ + etc/templates/Makefile \ + tools/Makefile \ + man/Makefile \ + expat/Makefile \ + mio/Makefile \ + subst/Makefile \ + sx/Makefile \ + util/Makefile \ + c2s/Makefile \ + resolver/Makefile \ + router/Makefile \ + s2s/Makefile \ + sm/Makefile \ + Doxyfile) diff --git a/contrib/README b/contrib/README new file mode 100644 index 00000000..d91f9ee8 --- /dev/null +++ b/contrib/README @@ -0,0 +1,17 @@ +This directory contains patches to the code which you may or may +not find useful. They are modifications to the main source which +would have had a detrimental affect towards jabberd2's goal of +100% xmpp compliance. Or they simply were not deemed appropriate +to add into the main source dist. However, there are situations +in which this code may be of use, so the patches are kept here. + +These patches are not maintained by jabberd2 team and may or may +not be out of date. You are have been warned. + +Good Luck + + +patch-flash-v2 +-------------- +This modifies c2s in order to allow macromedia's embrace and extend +proprietary mark-up language to inter-operate with j2. diff --git a/contrib/patch-flash-v2 b/contrib/patch-flash-v2 new file mode 100644 index 00000000..f752da7b --- /dev/null +++ b/contrib/patch-flash-v2 @@ -0,0 +1,173 @@ +diff -u /usr/local/src/jabberd-2.0s6/c2s/c2s.c c2s/c2s.c +--- /usr/local/src/jabberd-2.0s6/c2s/c2s.c Thu Dec 23 19:42:29 2004 ++++ c2s/c2s.c Thu Dec 23 19:41:20 2004 +@@ -20,6 +20,65 @@ + + #include "c2s.h" + ++/* ++ * M.Bootsma, LogicaCMG Hoofddorp, Netherlands ++ * October 2004 ++ * ++ * Added a patch for flash:stream support ++ * ++ * Flash is not 100% compatible with the XML stream standard: ++ * 1. it terminates every XML message with a '\0' ++ * 2. it terminates the stream header with a / ++ * (this would close the stream) ++ * 3. it starts the stream with a flash:stream header instead of ++ * a stream:stream header. ++ * ++ * The patch checks the first message of a starting session stream ++ * for any '\0'. If found it flags the session as a Flash session ++ * and replases the complete header with a Jabber compatible ++ * header. ++ * After that every incoming message is filtered and '\0' is ++ * replaced with ' '. ++ * For every outgoing message, a '\0' is appended and the response ++ * of the header is replaced for a flash friendly version ++ * ++ * The whole flash patch can be switched off by undefining CP2005_FLASH_PATCH ++ * in config.h(.in) ++ */ ++ ++#ifdef CP2005_FLASH_PATCH ++ ++#define FLASH_BUFFER_SIZE 256 ++ ++static const char caStreamHeader [] = ""; ++static const char caFlashHeader [] = ""; ++ ++static void ExtractValue(char *pMessage, char *pVariable, char *pValue) { ++ int iLen; ++ char *p; ++ char *pEnd; ++ ++ /* ++ * extract the value of an attribute from a XML message ++ * eg: <.... id='1234567890' ....> returns 1234567890 ++ */ ++ ++ p = strstr(pMessage, pVariable); ++ if (p != NULL) { ++ p += (strlen(pVariable) + 1); ++ /* find end of value, search for closing ' or " */ ++ pEnd = strchr(p, p [-1]); ++ iLen = pEnd - p; ++ if (iLen < FLASH_BUFFER_SIZE) { ++ memcpy(pValue, p, iLen); ++ pValue[iLen] = '\0'; ++ log_debug(ZONE, "++++ Extracted Var %s: [%s]\n", pVariable, pValue); ++ } ++ } ++} ++#endif ++ ++ + static int _c2s_client_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + sess_t sess = (sess_t) arg; + sx_buf_t buf = (sx_buf_t) data; +@@ -28,6 +93,12 @@ + nad_t nad; + char root[9]; + ++#ifdef CP2005_FLASH_PATCH ++ char *p, *pEnd; ++ char caHost[FLASH_BUFFER_SIZE]; ++ char caID[FLASH_BUFFER_SIZE]; ++#endif ++ + switch(e) { + case event_WANT_READ: + log_debug(ZONE, "want read"); +@@ -94,14 +165,74 @@ + return -1; + } + +- log_debug(ZONE, "read %d bytes", len); +- + buf->len = len; + ++#ifdef CP2005_FLASH_PATCH ++ /* check for 0 bytes in the first packet ++ * if found it must be a flash client ++ * remove any 0 in the data and ++ * the / that ends the data[len]; ++ ++ if (sess->s->state == state_NONE) { ++ /* stream is new, look for 0 bytes */ ++ p = memchr(buf->data, '\0', buf->len); ++ if ((p != NULL) && (p < pEnd)) { ++ log_debug(ZONE, "++++ Flash Stream detected\n%.*s", buf->len, buf->data); ++ sess->flash_client = 1; ++ ++ /* extract destination host */ ++ ExtractValue(buf->data, "to=", caHost); ++ ++ /* create normal stream:stream header, resize data buffer first */ ++ _sx_buffer_alloc_margin(buf, 0, sizeof(caStreamHeader) + strlen(caHost) + 8); ++ sprintf(buf->data, caStreamHeader, caHost); ++ buf->len = strlen(buf->data); ++ ++ log_debug(ZONE, "++++ Converted to\n%.*s", buf->len, buf->data); ++ } ++ } ++ ++ /* Check all other messages in the stream to remove \0's etc */ ++ if (sess->flash_client) ++ /* remove 0's from flash packets */ ++ for (p = buf->data; p < pEnd; p++) ++ if (*p == '\0') ++ *p = ' '; ++#endif ++ log_debug(ZONE, "read %d bytes", len); ++ + return len; + + case event_WRITE: + log_debug(ZONE, "writing to %d", sess->fd); ++ ++#ifdef CP2005_FLASH_PATCH ++ if (sess->flash_client) { ++ /* look for the header data, "len, buf->data); ++ ++ /* extract id from id="123456567778765" or id='45454545454' */ ++ ExtractValue(buf->data, "from=", caHost); ++ ExtractValue(buf->data, "id=", caID); ++ ++ /* create flash:stream header, realloc buffer first */ ++ _sx_buffer_alloc_margin(buf, 0, sizeof(caFlashHeader) + strlen(caHost) + strlen(caID) + 8); ++ sprintf(buf->data, caFlashHeader, caHost, caID); ++ buf->len = strlen(buf->data); ++ ++ log_debug(ZONE, "++++ Converted to %s", buf->data); ++ } ++ ++ /* add a 0 to flash packets */ ++ buf->data[buf->len] = '\0'; ++ buf->len++; ++ } ++#endif + + len = send(sess->fd, buf->data, buf->len, 0); + if(len >= 0) { +diff -u /usr/local/src/jabberd-2.0s6/c2s/c2s.h c2s/c2s.h +--- /usr/local/src/jabberd-2.0s6/c2s/c2s.h Tue Dec 7 18:38:05 2004 ++++ c2s/c2s.h Thu Dec 23 19:23:47 2004 +@@ -62,6 +62,10 @@ + int bound; + int active; + ++#ifdef CP2005_FLASH_PATCH ++ int flash_client; ++#endif ++ + nad_t result; + + int sasl_authd; /* 1 = they did a sasl auth */ diff --git a/docs/dev/c2s-authreg b/docs/dev/c2s-authreg new file mode 100644 index 00000000..9f20b9bf --- /dev/null +++ b/docs/dev/c2s-authreg @@ -0,0 +1,37 @@ +Simple authentication (iq:auth) flow: + +state: STREAM + +>>> iq:auth get +<<< iq:auth result containing auth methods +>>> iq:auth set + verify credentials + start session +<<< iq:auth result + +Registration (iq:register) flow: + +state: STREAM + +>>> iq:register get +<<< iq:register result containing required fields (username & password) +>>> iq:register set + create user +<<< iq:register + +Registration remove flow: + +state: OPEN + +>>> iq:register set containing remove tag + stop session + destroy user +<<< iq:register result +<<< disconnect + +Password change flow: + +state: OPEN +>>> iq:register set + save password +<<< iq:register result diff --git a/docs/dev/c2s-pipe-authenticator b/docs/dev/c2s-pipe-authenticator new file mode 100644 index 00000000..1eb57bf4 --- /dev/null +++ b/docs/dev/c2s-pipe-authenticator @@ -0,0 +1,48 @@ +c2s startup +pipe init + - create socketpair + - fork + - attach pair to stdio + - exec prog + +Commands return OK or NO, followed by return values + +Init: + +[auth process running] +<<< OK USER-EXISTS GET-PASSWORD CHECK-PASSWORD SET-PASSWORD GET-ZEROK SET-ZEROK CREATE-USER DESTROY-USER FREE + +>>> USER-EXISTS user [realm] +<<< OK +<<< NO + +>>> GET-PASSWORD user [realm] +<<< OK encoded_pass +<<< NO + +>>> CHECK-PASSWORD user encoded_pass [realm] +<<< OK +<<< NO + +>>> SET-PASSWORD user encoded_pass [realm] +<<< OK +<<< NO + +>>> GET-ZEROK user [realm] +<<< OK hash token seq# +<<< NO + +>>> SET-ZEROK user hash token seq# [realm] +<<< OK +<<< NO + +>>> CREATE-USER user [realm] +<<< OK +<<< NO + +>>> DELETE-USER user [realm] +<<< OK +<<< NO + +>>> FREE +[auth process exits] diff --git a/docs/dev/component-protocol b/docs/dev/component-protocol new file mode 100644 index 00000000..682f79f7 --- /dev/null +++ b/docs/dev/component-protocol @@ -0,0 +1,60 @@ +C == component, R == router. +This all happens in state_OPEN (ie after auth). +These elements are scoped by the 'http://jabberd.jabberstudio.org/ns/component/1.0' namespace. + +Joining the network +------------------- + +Bind a name to this component: + +C: [options] +R: + or +R: + +Options: + + Sets this component as the default route + Make this component a log sink (receives copies of all packets) + +Unbind a name: + +C: +R: + + +Domain advertisement +------------------- + +Domain online: + +R: + +Domain offline: + +R: + + +Sending packets +--------------- + +Send a unicast packet: + +C: + + + +Send a broadcast packet: + +C: + + + + +Throttling packets +------------------ + +(Un)throttle packets (toggle): + +C: +R: diff --git a/docs/dev/programmers-guide.xml b/docs/dev/programmers-guide.xml new file mode 100644 index 00000000..a57b33e9 --- /dev/null +++ b/docs/dev/programmers-guide.xml @@ -0,0 +1,665 @@ + + + + +
+ + + jabberd2 Programmers Guide + v0.01 for jabberd 2.0.0 alpha3 + + 2002 + Robert Norris + + This material may be distributed only subject to the terms and conditions set forth in the Open Publications License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/). + This document describes the various programming interfaces available in the jabberd2 system. + + +
+ expat - XML parsing library + The XML parsing requirements of the jabberd system are handled by the ubiquitous Expat library. Since we need a parser that is namespace aware, v1.95 is the version in use. + Expat is widely used and understood, and it doesn't make sense to repeat a long description of the library here. More information can be found on the Expat homepage. +
+ +
+ mio - Managed I/O + MIO is an event wrapper around UNIX file descriptors. The application can associate a callback function with each file descriptor it is interested in monitoring. When some kind of activity occurs on the file descriptor, the callback is fired. For network-driven applications such as jabberd, this is very useful, as it allows the main loop of the application to be reduced to a simple call into the MIO subsystem. + +
+ Data types + + + + MIO context - mio_t + mio_t is an opaque type that holds all the state information for the file descriptors being handled by the context. It is created by mio_new(), freed by mio_free(), and passed as the first argument to every other MIO function. + + + + MIO action (event) - mio_action_t + mio_action_t is an enumeration which is passed as the second argument to a callback, to inform the application of which event occured. It can take one of four values: + + action_ACCEPT - someone has connected to this (listening) file descriptor. + action_READ - this file descriptor has data waiting to be read. + action_WRITE - this file descriptor is able to be written to. + action_CLOSE - this file descriptor has been closed. + + + + + MIO handler (callback) - mio_handler_t + mio_handler_t is a prototype for a callback. A callback function should be of the following type: + typedef int (*mio_handler_t)(mio_t m, mio_action_t a, int fd, void *data, void *arg) + When called, the arguments passed to the callback are as follows: + + mio_t m - the MIO context that is managing the file descriptor. + mio_action_t a - the action that occured. + int fd - the file descriptor that this action occured on. Note that for action_ACCEPT, this argument will be hold the file descriptor of the new connection. + void *data - action-specific data. This is currently only used for action_ACCEPT - when this action occurs, data should be casted to char *, where it will contain a string representation of the remote IP address. + void *arg - this is the application-specific data that was registered at the same time the callback was registered with mio_fd() or mio_app(). + + The return type of the callback function is different depending on the action: + + action_ACCEPT - if the callback returns 0, the newly-accepted file descriptor will be tracked in the MIO context. If it returns non-zero, the connection will be closed. + action_READ - if the callback returns 0, no further read events will be fired to the callback until mio_read() is called on this file descriptor. If it returns non-zero, events will continue to be processed. + action_WRITE - if the callback returns 0, no further write events will be fired to the callback until mio_write() is called on this file descriptor. If it returns non-zero, events will continue to be processed. + action_CLOSE - the return value is ignored for this action. + + + + +
+ +
+ Creating/freeing a MIO context - mio_new(), mio_free() + + + + mio_t mio_new(int maxfd) + mio_new() creates a new MIO context. + This function takes the following arguments: + + int maxfd - the highest file descriptor (+1) that can be managed by this MIO context. + + This function returns the following values: + + NULL - creation of the MIO context failed for some reason. This is typically caused by a failure to allocate resources. + non-NULL - creation of the MIO context succeeded. The returned context is ready for use. + + + + + void mio_free(mio_t m) + mio_free() frees a MIO context. It does not close any file descriptors that are being managed by the MIO context. + This function takes the following arguments: + + mio_t m - pointer to the MIO context to free. + + This function returns nothing. + + + +
+ +
+ Setting the callback for a file descriptor - mio_fd(), mio_app() + + + + int mio_fd(mio_t m, int fd, mio_handler_t app, void *arg) + mio_fd() instructs MIO to begin tracking the given file descriptor. Note that the file descriptor will be set to non-blocking mode as a result of this call. + This function takes the following arguments: + + mio_t m - the MIO context to associate this file descriptor with. + int fd - the file descriptor to track. + mio_handler_t app - the callback to invoke when an event occurs. + void *arg - application-specific data to be passed to the callback. + + This function returns the following values: + + < 0 - the file descriptor number is too large for this MIO context to track. The maximum is set by mio_new(). + >= 0 - the file descriptor is being tracked. + + + + + void mio_app(mio_t m, int fd, mio_handler_t app, void *arg) + mio_app() resets the callback on the given file descriptor. This can only be called on a file descriptor that is being tracked by the MIO context. + This function takes the following arguments: + + mio_t m - the MIO context this file descriptor is associated with. + int fd - the file descriptor to reset the callback for. + mio_handler_t app - the callback to invoke when an event occurs. + void *arg - application-specific data to be passed to the callback. + + This function returns nothing. + + + +
+ +
+ Requesting notification about file descriptor events - mio_read(), mio_write() + + + + void mio_read(mio_t m, int fd) + mio_read() instructs MIO to process read events for the given file descriptor. When such an event occurs, the application callback for the file descriptor will be called with the action_READ event. + This function takes the following arguments: + + mio_t m - the MIO context this file descriptor is associated with. + int fd - the file descriptor to process read events for. + + This function returns nothing. + + + + void mio_write(mio_t m, int fd) + mio_write() instructs MIO to process write events for the given file descriptor. When such an event occurs, the application callback for the file descriptor will be called with the action_WRITE event. + This function takes the following arguments: + + mio_t m - the MIO context this file descriptor is associated with. + int fd - the file descriptor to process write events for. + + This function returns nothing. + + + +
+ +
+ Closing a file descriptor - mio_close() + + + + void mio_close(mio_t m, int fd) + mio_close() is a wrapper around close() that also stops the file descriptor from being tracked by the MIO context. You should use this in place of close() for a MIO-managed file descriptor. + This function takes the following arguments: + + mio_t m - the MIO context this file descriptor is associated with. + int fd - the file descriptor to close. + + This function returns nothing. + + + +
+ +
+ Creating a new file descriptor - mio_connect(), mio_listen() + + + + int mio_connect(mio_t m, int port, char *hostip, mio_handler_t app, void *arg) + mio_connect() initiates a connection to the given ip/port. This function returns immediately, and the connect continues in the background (in non-blocking mode). The application should call mio_read() and/or mio_write() after calling mio_connect() in order to receive events after the connection has completed. + If the connect fails, the callback will be called with the action_CLOSE event. + This function takes the following arguments: + + mio_t m - the MIO context that will track this connection. + int port - the remote port to connect to. + char *hostip - string representation of the remote IP address to connect to. + mio_handler_t app - the callback to invoke when an event occurs. + void *arg - application-specific data to be passed to the callback. + + This function returns the following values: + + < 0 - the file descriptor creation or the connect init failed for some reason. errno may have more information. + >= 0 - the new file descriptor. + + + + + int mio_listen(mio_t m, int port, char *sourceip, mio_handler_t app, void *arg) + mio_listen() starts a listening socket on the given ip/port. The callback will be called with the action_ACCEPT action when a new connection is initiated. + This function takes the following arguments: + + mio_t m - the MIO context that will track this socket, and newly-accepted connections. + int port - the local port to listen on. + char *sourceip - string representation of the local IP address to listen on. + mio_handler_t app - the callback to invoke when an event occurs. + void *arg - application-specific data to be passed to the callback. + + This function returns the following values: + + < 0 - the file descriptor creation or the listen init failed for some reason. errno may have more information. + >= 0 - the file descriptor of the listening socket. + + + + +
+ +
+ Process pending events on file descriptors - mio_run() + + + + void mio_run(mio_t m, int timeout) + mio_run() processes pending events on file descriptors being tracked by the given MIO context. It will fire the associated callback if anything interesting has happened to a file descriptor. + This function takes the following arguments: + + mio_t m - the MIO context to process. + int timeout - Number of seconds to wait for events for. The call will block for at least this long. To do one check, then return (ie non-blocking behaviour), set this to 0. + + This function returns nothing. + + + +
+ +
+ Extending MIO - backends for event-readiness APIs + TODO +
+ +
+ +
+ util - Support utilities + jabberd2 provides a variety of special-purpose utility libraries to aid the programmer with common functions. Some of them contain dependencies on other utilities and in one case (config), also a dependency on Expat. These dependencies are noted. + +
+ access - IP-based access controls + +
+ Data types + + + + access context - access_t + + + +
+ +
+ Creating/freeing an access context - access_new(), access_free() + + + + access_t access_new(int order) + + + + void access_free(access_t access) + + + +
+ +
+ Adding an access control rule - access_allow(), access_deny() + + + + int access_allow(access_t access, char *ip, char *mask) + + + + int access_deny(access_t access, char *ip, char *mask) + + + +
+ +
+ Checking whether are not an IP address matches a rule - access_check() + + + + + int access_check(access_t access, char *ip) + + + +
+ +
+ +
+ config - XML config files + TODO +
+ +
+ jid - Jabber ID manipulation + TODO +
+ +
+ log - Logging (file/syslog) + +
+ Data types + + + + log context - log_t + + + +
+ +
+ Creating/freeing a log context - log_new(), log_free() + + + + log_t log_new(int syslog, char *ident, int facility) + + + + void log_free(log_t log) + + + +
+ +
+ Writing data to a log - log_write() + + + + log_t log_write(log_t log, int level, const char *msgfmt, ...) + + + +
+ +
+ +
+ nad - Lightweight XML DOM + TODO +
+ +
+ pool - Memory pools + TODO +
+ +
+ rate - Rate controls + +
+ Data types + + + + rate context - rate_t + + + +
+ +
+ Creating/freeing a rate context - rate_new(), rate_free() + + + + rate_t rate_new(int total, int seconds, int wait) + + + + void rate_free(rate_t rt) + + + +
+ +
+ Updated rate counts - rate_add(), rate_reset() + + + + void rate_add(rate_t rt, int count) + + + + void rate_reset(rate_t rt) + + + +
+ +
+ Checking rate limits - rate_check(), rate_left() + + + + int rate_check(rate_t rt) + + + + int rate_left(rate_t rt) + + + +
+ +
+ +
+ serial - Data serialisation + +
+ Retrieving values from a buffer - ser_string_get(), ser_int_get() + + + + int ser_string_get(char **dest, int *source, char *buf, int len) + + + + int ser_int_get(int *dest, int *source, char *buf, int len) + + + +
+ +
+ Adding values to a buffer - ser_string_set(), ser_int_set() + + + + void ser_string_set(char *source, int *dest, char **buf, int *len) + + + + void ser_int_set(int source, int *dest, char **buf, int *len) + + + +
+ +
+ +
+ sha - SHA1 hash generation + +
+ Generating a SHA1 hash - shahash(), shahash_r() + + + + char *shahash(char *str) + + + + void shahash_r(const char *str, char hashbuf[41] + + + +
+
+ +
+ str - String manipulation +
+ +
+ spool - String pools + +
+ Data types + + + + spool context - spool + + + +
+ +
+ Creating a spool context - spool_new() + + + + spool spool_new(pool p) + + + +
+ +
+ Adding strings to a spool context - spool_add(), spooler() + + + + void spool_add(spool s, char *str) + + + + void spooler(spool s, ...) + + + +
+ +
+ Getting the contents of a spool context - spool_print() + + + + char *spool_print(spool s) + + + +
+ +
+ Concatenating strings on the fly - spools() + + + + void spools(pool p, ...) + + + +
+ +
+ +
+ xhash - Hash tables + TODO +
+ +
+ +
+ sx - XML streams abstraction + +
+ Data types + TODO +
+ +
+ Creating/freeing an XML stream - sx_new(), sx_free(), sx_env_new(), sx_env_free() + TODO +
+ +
+ Beginning and ending an XML stream - sx_client_init(), sx_server_init(), sx_auth(), sx_close() + TODO +
+ +
+ Using an XML stream + TODO. + Creating a stream. + Connecting it and performing any needed authorizations or negotiations. + Writing nads to the stream. Processing nads parsed by the stream. + Shutting down a stream. + Handling I/O for the stream. Dealing with network or protocol errors. + +
+ +
+ Providing an <type>sx_callback_t</type> function + An XML stream communicates events and data to the rest of the program by invoking the callback function which was given as an argument to sx_new(). The first two arguments of the callback function are the sx_t which is invoking the callback, and an enumerated value (sx_event_t) which indicates the reason the callback was invoked. The interpretation of the third argument (a void *) and the return value (an integer) depends on the value of the event argument. + The event_WANT_READ and event_WANT_WRITE events indicate that the xml stream is expecting input from the underlying octet-stream or has data it wishes to write to that stream. The callback should invoke either sx_can_read() or sx_can_write() as appropriate. If the read or write can be performed immediately without blocking, it can be invoked directly from the callback; otherwise, the callback should schedule the function to be called when data (or buffer space) is available. The data argument and the return value are not used for these event types. + event_READ and event_WRITE are requests for the callback to perform IO on behalf of the xml stream. The data argument is a pointer to an sx_buf_t. For event_READ, the callback should read into the specified buffer, adjust the buffer's len to indicate the number of bytes read, and return a nonnegative number to indicate success. For event_WRITE, the callback should return the number of bytes written from the buffer. A negative return value indicates that an error (including end-of-file) has occurred and the stream must be shut down. + TODO: nonblocking IO and event loops; use of mio_* functions. + event_STREAM, event_OPEN, event_CLOSED, event_ERROR: TODO. Being notified of changes in the stream's state. + event_PACKET: TODO. Processing a packet received by the stream. +
+
+ Implementing a stream plugin + TODO: What a plugin is for and what it can do. Existing stream plugins. How to create a new stream plugin. Processing sent and received bytes. Processing sent and received nads. +
+
+ Buffer management functions used with XML streams + TODO: sx_buf_t functions and memory management. +
+
+ +
+ Extending the session manager + +
+ Storage drivers + TODO +
+ +
+ Protocol modules + TODO +
+ +
+ +
+ Extending the client-to-server connector + +
+ Authenticator modules + TODO +
+ +
+ Using the pipe authenticator + TODO +
+ +
+ +
+ Internal protocols + +
+ Session control protocol + TODO +
+ +
+ DNS resolution protocol + TODO +
+ +
+ +
+ diff --git a/docs/dev/resolver-protocol b/docs/dev/resolver-protocol new file mode 100644 index 00000000..f9c1eaf2 --- /dev/null +++ b/docs/dev/resolver-protocol @@ -0,0 +1,9 @@ + + + + + + + 130.194.2.47 + + diff --git a/docs/dev/sm-c2s-protocol b/docs/dev/sm-c2s-protocol new file mode 100644 index 00000000..34c14bf5 --- /dev/null +++ b/docs/dev/sm-c2s-protocol @@ -0,0 +1,78 @@ +If any action fails, the component returns it with sm:failed='1' on the +session element. We don't use the route type at all anymore, that is +reserved for _routing_ errors. This has the nice side effect of allowing +us to distinguish between "session ended" and "sm crashed". + +No bounce must result from a failed action. + +The namespace URL is "http://jabberd.jabberstudio.org/ns/session/1.0" + + +c2s->sm: start session + + + + + +sm->c2s: session started + + + + + + +c2s->sm: end session + + + + + +sm->c2s: session ended + + + + + + +c2s->sm: send packet + + + + ... + + + +sm->c2s: send packet + + + + ... + + + + +c2s->sm: create user + + + + + +sm->c2s: user created + + + + + + +c2s->sm: delete user + + + + + +sm->c2s: user deleteed + + + + + diff --git a/docs/dev/sm-presence-logic b/docs/dev/sm-presence-logic new file mode 100644 index 00000000..8f8a0d92 --- /dev/null +++ b/docs/dev/sm-presence-logic @@ -0,0 +1,108 @@ +This is the logic employed by the presence tracker. The business rules +are implemented in the SM core (sm/pres.c). The triggers (protocol +specifics) are implemented in the Presence module (sm/mod_presence.c). + +XMPP version (no "invisible" presence) +-------------------------------------- + +There are three sets of JIDs: + T: trusted - these may see our presence ("from" or "both" in roster) + A: available - these have been sent directed available presence + E: error - these bounced presence updates + +Business rules: + + B1. Broadcasting available presence: + forward to all in T, unless or E + + B2. Broadcasting unavailable presence: + forward to all in T and A, unless in E + clear A and E + + B3. Remote presence probe: + respond if in T + remove from E + + B4. Remote presence update: + forward to all sessions with priority >= 0 + + B5. Remote presence error: + add to E + remove from A + + B6. Directed available presence: + forward + add to A (unless in T) + remove from E + + B7. Directed unavailable presence: + forward + remove from A and E + +Triggers: + + T1. + T2. + T3. + T4. or + T5. + T6. + T7. + + +Jabber version (XMPP + "invisible" presence) +-------------------------------------------- + +There are three sets of JIDs: + T: trusted - these may see our presence ("from" or "both" in roster) + A: available - these have been sent directed available presence + E: error - these bounced presence updates + +There is also an "invisible" flag, I. + +Business rules: + + B1. Broadcasting available presence: + forward to all in T, unless in E + clear I + + B2. Broadcasting unavailable presence: + forward to all in T and A, unless in E + clear A and E + clear I + + B3. Broadcasting invisible presence: + send unavailable to all in T, unless in A or E + set I + + B4. Remote presence probe: + respond if in T and I clear + respond if in T and in A and I set + remove from E + + B5. Remote presence update: + forward to all sessions with priority >= 0 + + B6. Remote presence error: + add to E + remove from A + + B7. Directed available presence: + forward + add to A (unless in T) + remove from E + + B8. Directed unavailable presence: + forward + remove from A and E + +Triggers: + + T1. + T2. + T3. () + T4. + T5. or + T6. + T7. + T8. diff --git a/docs/dev/sm-storage b/docs/dev/sm-storage new file mode 100644 index 00000000..ee5ce24e --- /dev/null +++ b/docs/dev/sm-storage @@ -0,0 +1,122 @@ +Writing a storage module for jadlsm +----------------------------------- + +The storage manager presents a hash-style interface to underlying data +stores. The hash "key" is represented by two strings - a data type and +and arbitrary string (often a JID). + +Associated with each key is a set of values, each with an index (ie an +array). There are five operations that can be performed a particular key: + + put - appends a data item to the end of the array + get - retrieves the data item at the given index + zap - deletes the data item at the given index, and shifts all the + items to the left of the given index back one, to fill in + the hole + replace - replaces the data item at the given index with another data + item + count - counts the number of data items in the array for a given key + +Storage module code must be stored in a single file called storage_foo.c +(where foo is the name of your module), and must export a single +function, foo_init, which is of the form: + + st_ret_t foo_init(st_module_st mod); + +This function should perform any module-wide initialisation. This +might include reading config settings, connecting to a database, etc. +The module should fill out the fields of the module structure mod. mod +contains seven function pointers which your module to set to point to +the implementation of each function. There is also a field called +private, which is a void pointer and can be used to store any +module-specific data. + +mod has a field called st, which references the storage manager instance +for the session manager. The lsm field of st can be used to get to the +session manager instance, and from there, to the config system, the log +system, the nad cache, and any other session manager systems. + +Your module should implement seven functions which are referenced by the +pointers in mod. These are: + + st_ret_t db_add_type(st_module_t mod, char *type) + +Called to signal to the module that it will be required to manage data +of this type. + + st_ret_t db_put(st_module_t mod, char *key, char *type, char *buf, int buflen) + +Stores a single data item of this type in the array associated with the +key. If the key exists, the item is added to the end of the array. If +the key doesn't exist, it is created. + + st_ret_t db_get(st_module_t mod, char *key, char *type, int num, char **buf, int *buflen) + +Returns data item #num of this type from the array associated with the +key. A buffer should be allocated to hold the returned data, and the +buffer and the length returned in buf and buflen. It is the +responsibility of the caller to free the returned buffer. + + st_ret_t db_zap(st_module_t mod, char *key, char *type, int num) + +Deletes data item #num of this type from the array associated with the +key. Items following the deleted key are moved left one space in the +array (to fill in the gap); eg: + + 0 1 2 3 + foo bar baz last + +Deleting item 2 would result in + + 0 1 2 + foo bar last + + st_ret_t db_replace(st_module_t mod, char *key, char *type, int num, char *buf, int buflen) + +This is effectively a combination of zap and delete, except that the new +data item is added in-place; ie it replaced the specified value without +moving any of the other items. + + st_ret_t db_count(st_module_t mod, char *key, char *type, int *count) + +Returns the number of items of this type in the array associated with +the key. + +st_ret_t is an enumerated type which is used to flag the result of the +function call to the caller. Functions should return one of four values: + + st_SUCCESS - the call completed successfully + st_FAILURE - something went wrong and the call could not complete + st_NOTFOUND - the requested storage key does not exist + (only for get, zap, replace and count) + st_NOTIMPL - the call is not implemented + (or, this module can not handle the given type) + +Once completed, your storage module can be included in the build process +by specifying --storage-module=foo at compile time. + +The type configuration for your module is done in jadlsm.xml. You should +add a section for your module in the section. For example, to +make your module handle the "message" type, you'd use: + + + message + + +You can also have multiple types for a module: + + + message + last + + +Or your module can be set up as the default for all undeclared types: + + + + + +This config file section is a good place to include any configuration +that your module requires (eg database location, auth credentials, etc). + +See storage_db.c for an example implementation. diff --git a/docs/dev/sm-storage-types b/docs/dev/sm-storage-types new file mode 100644 index 00000000..c7f5e48d --- /dev/null +++ b/docs/dev/sm-storage-types @@ -0,0 +1,99 @@ +This is a list of the DB collections types that the SM uses, and their fields. + +type: active +user: core +fields: time os_type_INTEGER + +If a user has a stored value for "active" then they "exist" for the purposes +of routing and delivery. The value is the time (in seconds since the epoch) +that they were first "created". This value is stored in user->active, which +is used by mod_offline and mod_roster to determine if it should bother +processing messages and subscription requests. + + +type: logout +user: mod_iq_last +fields: time os_type_INTEGER + +This stores the time (in seconds since the epoch) of the last session that +logged out. This is used to provide an iq:last response when there are no +sessions online. + + +type: roster-items +user: mod_roster +fields: jid os_type_STRING + name os_type_STRING + to os_type_BOOLEAN + from os_type_BOOLEAN + ask os_type_BOOLEAN + +This stores the roster items that make a user's roster. + + +type: roster-groups +user: mod_roster +fields: jid os_type_STRING + group os_type_STRING + +This stores the groups associated with each of a user's roster items. + + +type: vcard +user: mod_iq_vcard +fields: + fn os_type_STRING + nickname os_type_STRING + url os_type_STRING + tel os_type_STRING + email os_type_STRING + title os_type_STRING + role os_type_STRING + bday os_type_STRING + desc os_type_STRING + n-given os_type_STRING + n-family os_type_STRING + adr-street os_type_STRING + adr-extadd os_type_STRING + adr-locality os_type_STRING + adr-region os_type_STRING + adr-pcode os_type_STRING + adr-country os_type_STRING + org-orgname os_type_STRING + org-orgunit os_type_STRING + +Stores the various fields the make up the user's vcard. + + +type: queue +user: mod_offline +fields: xml os_type_NAD + +This stores messages and s10n requests that are waiting to be delivered +to the user. Each DB row contains a single complete packet. + + +type: private +user: mod_iq_private +fields: ns os_type_STRING + xml os_type_NAD + +This stores user private data (iq:private). ns holds the namespace (for +fast lookup), and xml holds the complete packet (actually a copy of the +original IQ set that stored the packet). + + +type: motd-messages +user: mod_announce +fields: xml os_type_NAD + +This stores the current "message of the day" message. There is only one +motd at any one time. + + +type: motd-times +user: mod_announce +fields: time os_type_INTEGER + +This stores the time of the last motd message that a user received. It +is used to determine whether to send the current motd to the user. diff --git a/docs/layout b/docs/layout new file mode 100644 index 00000000..e6d1d8fd --- /dev/null +++ b/docs/layout @@ -0,0 +1,16 @@ +jabber2/ + + docs/ - Documentation + dev/ - Hacker docs + + expat/ - Expat (XML parsing library) + idn/ - GNU Libidn (stringprep) + mio/ - Managed Input/Ouput (FD event processor) + sx/ - Streams for XML (Jabber connection / stream library) + util/ - Utilities (config, logging, NADs, hashtables, etc) + + c2s/ - Client-to-server + resolver/ - Asynchronous DNS resolver daemon + router/ - XML router + sm/ - Session manager + s2s/ - Server-to-server diff --git a/etc/.cvsignore b/etc/.cvsignore new file mode 100644 index 00000000..963406d1 --- /dev/null +++ b/etc/.cvsignore @@ -0,0 +1,9 @@ +Makefile +Makefile.in +c2s.xml.dist +jabberd.cfg.dist +resolver.xml.dist +router.xml.dist +router-users.xml.dist +s2s.xml.dist +sm.xml.dist diff --git a/etc/Makefile.am b/etc/Makefile.am new file mode 100644 index 00000000..bdaa42bf --- /dev/null +++ b/etc/Makefile.am @@ -0,0 +1,36 @@ +sysconf_DATA = c2s.xml.dist resolver.xml.dist router.xml.dist s2s.xml.dist sm.xml.dist jabberd.cfg.dist router-users.xml.dist +EXTRA_DIST = c2s.xml.dist.in resolver.xml.dist.in router.xml.dist.in s2s.xml.dist.in sm.xml.dist.in jabberd.cfg.dist.in router-users.xml.dist.in + +SUBDIRS = templates + +jabberd_bin = router resolver sm s2s c2s + +edit = sed \ + -e 's,@sysconfdir\@,$(sysconfdir),g' \ + -e 's,@localstatedir\@,$(localstatedir),g' \ + -e 's,@bindir\@,$(bindir),g' + +$(sysconf_DATA): + @echo "generating $@ from $@.in"; \ + edit='$(edit)'; \ + list='$(jabberd_bin)'; for p in $$list; do \ + bin=`echo "$$p" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + edit="$$edit -e s,@jabberd_$$p\_bin\\@,$$bin,g"; \ + done; \ + rm -f $@ $@.tmp; \ + eval "$$edit < $@.in > $@.tmp"; \ + mv $@.tmp $@ + +install-data-hook: + @list='$(sysconf_DATA)'; for p in $$list; do \ + dest=`echo $$p | sed -e s/.dist//`; \ + if test -f $(DESTDIR)$(sysconfdir)/$$dest; then \ + echo "$@ will not overwrite existing $(DESTDIR)$(sysconfdir)/$$dest"; \ + else \ + echo " $(INSTALL_DATA) $$p $(DESTDIR)$(sysconfdir)/$$dest"; \ + $(INSTALL_DATA) $$p $(DESTDIR)$(sysconfdir)/$$dest; \ + fi; \ + done + +clean-local: + rm -f $(sysconf_DATA) diff --git a/etc/c2s.xml.dist.in b/etc/c2s.xml.dist.in new file mode 100644 index 00000000..d28f71ed --- /dev/null +++ b/etc/c2s.xml.dist.in @@ -0,0 +1,361 @@ + + + + c2s + + + @localstatedir@/jabberd/pid/c2s.pid + + + + + 127.0.0.1 + 5347 + + + jabberd + secret + + + + + + + + 3 + + + 3 + + + 2 + + + + + + + jabberd/c2s + + + local3 + + + + + + + + + localhost + + + + 0.0.0.0 + + + 5222 + + + + + + + + + + + + + + + + + + + + + 1024 + + + + + 0 + + + 0 + + + + + + allow,deny + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + + + + + + + mysql + + + + + + + + Enter a username and password to register with this server. + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 3306 + + + jabberd2 + + + jabberd2 + secret + + + + + + localhost + 5432 + + + jabberd2 + + + jabberd2 + secret + + + + + + @localstatedir@/jabberd/db + + + + + + + + + ldap.example.com + 389 + + + + + + + + + + + + + + + uid + + + o=Company.com + o=Example Corp. + + + + + + @bindir@/pipe-auth.pl + + + + + diff --git a/etc/jabberd.cfg.dist.in b/etc/jabberd.cfg.dist.in new file mode 100644 index 00000000..a212f329 --- /dev/null +++ b/etc/jabberd.cfg.dist.in @@ -0,0 +1,19 @@ +# +# jabberd config file +# +# +# This file tells the jabberd wrapper what programs to launch, +# and the config files to launch them with. If the config file +# is left out, then the system default will be used. +# +# To run multiple Session Managers, just list them all seperatly +# and provide the path to the appropriate config files. +# +# program [ path to config file ] +# + +@jabberd_router_bin@ @sysconfdir@/router.xml +@jabberd_resolver_bin@ @sysconfdir@/resolver.xml +@jabberd_sm_bin@ @sysconfdir@/sm.xml +@jabberd_s2s_bin@ @sysconfdir@/s2s.xml +@jabberd_c2s_bin@ @sysconfdir@/c2s.xml diff --git a/etc/resolver.xml.dist.in b/etc/resolver.xml.dist.in new file mode 100644 index 00000000..664ca1f7 --- /dev/null +++ b/etc/resolver.xml.dist.in @@ -0,0 +1,81 @@ + + + + resolver + + + @localstatedir@/jabberd/pid/resolver.pid + + + + + 127.0.0.1 + 5347 + + + jabberd + secret + + + + + + + + 3 + + + 3 + + + 2 + + + + + + + jabberd/resolver + + + local3 + + + + + + + + + _xmpp-server._tcp + + + _jabber._tcp + + + + + + diff --git a/etc/router-users.xml.dist.in b/etc/router-users.xml.dist.in new file mode 100644 index 00000000..2d8a0d08 --- /dev/null +++ b/etc/router-users.xml.dist.in @@ -0,0 +1,8 @@ + + + + jabberd + secret + + diff --git a/etc/router.xml.dist.in b/etc/router.xml.dist.in new file mode 100644 index 00000000..a3eea6b7 --- /dev/null +++ b/etc/router.xml.dist.in @@ -0,0 +1,180 @@ + + + + router + + + @localstatedir@/jabberd/pid/router.pid + + + + + jabberd/router + + + local3 + + + + + + + + + 0.0.0.0 + + + 5347 + + + @sysconfdir@/router-users.xml + + + secret + + + + + + + + + 60 + + + 0 + + + + + + + 1024 + + + + + 0 + + + 0 + + + + + + allow,deny + + + + + + + + + + + + + + + + + + + + + + + jabberd + + + + + + + + + + + + + diff --git a/etc/s2s.xml.dist.in b/etc/s2s.xml.dist.in new file mode 100644 index 00000000..0957ccba --- /dev/null +++ b/etc/s2s.xml.dist.in @@ -0,0 +1,158 @@ + + + + s2s + + + @localstatedir@/jabberd/pid/s2s.pid + + + + + 127.0.0.1 + 5347 + + + jabberd + secret + + + + + + + + + + + 3 + + + 3 + + + 2 + + + + + + + jabberd/s2s + + + local3 + + + + + + + + + 0.0.0.0 + 5269 + + + resolver + + + + + + + + + + + + + + + + + + + 60 + + + 60 + + + 86400 + + + 0 + + + + diff --git a/etc/sm.xml.dist.in b/etc/sm.xml.dist.in new file mode 100644 index 00000000..8691a8ea --- /dev/null +++ b/etc/sm.xml.dist.in @@ -0,0 +1,396 @@ + + + + localhost + + + @localstatedir@/jabberd/pid/sm.pid + + + + + 127.0.0.1 + 5347 + + + jabberd + secret + + + + + + + + 3 + + + 3 + + + 2 + + + + + + + jabberd/sm + + + local3 + + + + + + + + + mysql + + + + + + + + + localhost + 3306 + + + jabberd2 + + + jabberd2 + secret + + + + + + + + + localhost + 5432 + + + jabberd2 + + + jabberd2 + secret + + + + + + + + + @localstatedir@/jabberd/db + + + + + + + + + localhost + 1521 + + + jabberd2 + + + jabberd2 + secret + + + + + + + + admin@localhost + + + + + + + + + + + + + + + + + + + + + iq-last + + + + + validate + privacy + roster + vacation + iq-vcard + iq-private + disco + offline + announce + presence + deliver + + + + + + + + session + validate + presence + privacy + + + + + privacy + + + + + iq-last + iq-time + iq-version + disco + announce + help + echo + + + + + roster + presence + iq-vcard + deliver + vacation + offline + disco-publish + iq-last + + + + + session + disco + + + + + active + roster + privacy + disco-publish + vacation + + + + + active + template-roster + + + + + active + announce + disco-publish + offline + privacy + roster + vacation + iq-last + iq-private + iq-vcard + + + + + + + + + + server + im + Jabber IM server + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/etc/storage.xml.dist.in b/etc/storage.xml.dist.in new file mode 100644 index 00000000..cbad0efd --- /dev/null +++ b/etc/storage.xml.dist.in @@ -0,0 +1,63 @@ + + + + storage + + + @localstatedir@/jabberd/pid/storage.pid + + + + + 127.0.0.1 + 5347 + + + jabberd + secret + + + + + + + + 3 + + + 3 + + + 2 + + + + + + + jabberd/storage + + + local3 + + + + + + diff --git a/etc/templates/.cvsignore b/etc/templates/.cvsignore new file mode 100644 index 00000000..896d840d --- /dev/null +++ b/etc/templates/.cvsignore @@ -0,0 +1,2 @@ +Makefile Makefile.in +roster.xml.dist diff --git a/etc/templates/Makefile.am b/etc/templates/Makefile.am new file mode 100644 index 00000000..8afe6f5b --- /dev/null +++ b/etc/templates/Makefile.am @@ -0,0 +1,29 @@ +templatesdir = $(sysconfdir)/templates + +templates_DATA = roster.xml.dist +EXTRA_DIST = roster.xml.dist.in + +edit = sed \ + -e 's,@sysconfdir\@,$(sysconfdir),g' \ + -e 's,@localstatedir\@,$(localstatedir),g' \ + -e 's,@bindir\@,$(bindir),g' + +$(templates_DATA): + @echo "generating $@ from $@.in"; \ + rm -f $@ $@.tmp; \ + $(edit) < $@.in > $@.tmp; \ + mv $@.tmp $@ + +install-data-hook: + @list='$(templates_DATA)'; for p in $$list; do \ + dest=`echo $$p | sed -e s/.dist//`; \ + if test -f $(DESTDIR)$(templatesdir)/$$dest; then \ + echo "$@ will not overwrite existing $(DESTDIR)$(templatesdir)/$$dest"; \ + else \ + echo " $(INSTALL_DATA) $$p $(DESTDIR)$(templatesdir)/$$dest"; \ + $(INSTALL_DATA) $$p $(DESTDIR)$(templatesdir)/$$dest; \ + fi; \ + done + +clean-local: + rm -f $(templates_DATA) diff --git a/etc/templates/roster.xml.dist.in b/etc/templates/roster.xml.dist.in new file mode 100644 index 00000000..387e3d8e --- /dev/null +++ b/etc/templates/roster.xml.dist.in @@ -0,0 +1,7 @@ + + + + diff --git a/expat/.cvsignore b/expat/.cvsignore new file mode 100644 index 00000000..6e5ca7ed --- /dev/null +++ b/expat/.cvsignore @@ -0,0 +1,6 @@ +Makefile +Makefile.in +.deps +.libs +*.lo +*.la diff --git a/expat/Makefile.am b/expat/Makefile.am new file mode 100644 index 00000000..bc975e8c --- /dev/null +++ b/expat/Makefile.am @@ -0,0 +1,22 @@ +CFLAGS=-DHAVE_EXPAT_CONFIG_H + +noinst_LTLIBRARIES = libexpat.la + +noinst_HEADERS = ascii.h \ + asciitab.h \ + expat.h \ + expat_config.h \ + iasciitab.h \ + internal.h \ + latin1tab.h \ + nametab.h \ + utf8tab.h \ + xmlrole.h \ + xmltok.h \ + xmltok_impl.h \ + winconfig.h + +libexpat_la_SOURCES = xmlparse.c xmlrole.c xmltok.c +libexpat_la_LIBADD = @LDFLAGS@ + +EXTRA_DIST = xmltok_impl.c xmltok_ns.c diff --git a/expat/ascii.h b/expat/ascii.h new file mode 100644 index 00000000..337e5bb7 --- /dev/null +++ b/expat/ascii.h @@ -0,0 +1,85 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +#define ASCII_A 0x41 +#define ASCII_B 0x42 +#define ASCII_C 0x43 +#define ASCII_D 0x44 +#define ASCII_E 0x45 +#define ASCII_F 0x46 +#define ASCII_G 0x47 +#define ASCII_H 0x48 +#define ASCII_I 0x49 +#define ASCII_J 0x4A +#define ASCII_K 0x4B +#define ASCII_L 0x4C +#define ASCII_M 0x4D +#define ASCII_N 0x4E +#define ASCII_O 0x4F +#define ASCII_P 0x50 +#define ASCII_Q 0x51 +#define ASCII_R 0x52 +#define ASCII_S 0x53 +#define ASCII_T 0x54 +#define ASCII_U 0x55 +#define ASCII_V 0x56 +#define ASCII_W 0x57 +#define ASCII_X 0x58 +#define ASCII_Y 0x59 +#define ASCII_Z 0x5A + +#define ASCII_a 0x61 +#define ASCII_b 0x62 +#define ASCII_c 0x63 +#define ASCII_d 0x64 +#define ASCII_e 0x65 +#define ASCII_f 0x66 +#define ASCII_g 0x67 +#define ASCII_h 0x68 +#define ASCII_i 0x69 +#define ASCII_j 0x6A +#define ASCII_k 0x6B +#define ASCII_l 0x6C +#define ASCII_m 0x6D +#define ASCII_n 0x6E +#define ASCII_o 0x6F +#define ASCII_p 0x70 +#define ASCII_q 0x71 +#define ASCII_r 0x72 +#define ASCII_s 0x73 +#define ASCII_t 0x74 +#define ASCII_u 0x75 +#define ASCII_v 0x76 +#define ASCII_w 0x77 +#define ASCII_x 0x78 +#define ASCII_y 0x79 +#define ASCII_z 0x7A + +#define ASCII_0 0x30 +#define ASCII_1 0x31 +#define ASCII_2 0x32 +#define ASCII_3 0x33 +#define ASCII_4 0x34 +#define ASCII_5 0x35 +#define ASCII_6 0x36 +#define ASCII_7 0x37 +#define ASCII_8 0x38 +#define ASCII_9 0x39 + +#define ASCII_TAB 0x09 +#define ASCII_SPACE 0x20 +#define ASCII_EXCL 0x21 +#define ASCII_QUOT 0x22 +#define ASCII_AMP 0x26 +#define ASCII_APOS 0x27 +#define ASCII_MINUS 0x2D +#define ASCII_PERIOD 0x2E +#define ASCII_COLON 0x3A +#define ASCII_SEMI 0x3B +#define ASCII_LT 0x3C +#define ASCII_EQUALS 0x3D +#define ASCII_GT 0x3E +#define ASCII_LSQB 0x5B +#define ASCII_RSQB 0x5D +#define ASCII_UNDERSCORE 0x5F diff --git a/expat/asciitab.h b/expat/asciitab.h new file mode 100644 index 00000000..79a15c28 --- /dev/null +++ b/expat/asciitab.h @@ -0,0 +1,36 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +/* 0x00 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x04 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x08 */ BT_NONXML, BT_S, BT_LF, BT_NONXML, +/* 0x0C */ BT_NONXML, BT_CR, BT_NONXML, BT_NONXML, +/* 0x10 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x14 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x18 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x1C */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x20 */ BT_S, BT_EXCL, BT_QUOT, BT_NUM, +/* 0x24 */ BT_OTHER, BT_PERCNT, BT_AMP, BT_APOS, +/* 0x28 */ BT_LPAR, BT_RPAR, BT_AST, BT_PLUS, +/* 0x2C */ BT_COMMA, BT_MINUS, BT_NAME, BT_SOL, +/* 0x30 */ BT_DIGIT, BT_DIGIT, BT_DIGIT, BT_DIGIT, +/* 0x34 */ BT_DIGIT, BT_DIGIT, BT_DIGIT, BT_DIGIT, +/* 0x38 */ BT_DIGIT, BT_DIGIT, BT_COLON, BT_SEMI, +/* 0x3C */ BT_LT, BT_EQUALS, BT_GT, BT_QUEST, +/* 0x40 */ BT_OTHER, BT_HEX, BT_HEX, BT_HEX, +/* 0x44 */ BT_HEX, BT_HEX, BT_HEX, BT_NMSTRT, +/* 0x48 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x4C */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x50 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x54 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x58 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_LSQB, +/* 0x5C */ BT_OTHER, BT_RSQB, BT_OTHER, BT_NMSTRT, +/* 0x60 */ BT_OTHER, BT_HEX, BT_HEX, BT_HEX, +/* 0x64 */ BT_HEX, BT_HEX, BT_HEX, BT_NMSTRT, +/* 0x68 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x6C */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x70 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x74 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x78 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_OTHER, +/* 0x7C */ BT_VERBAR, BT_OTHER, BT_OTHER, BT_OTHER, diff --git a/expat/expat.h b/expat/expat.h new file mode 100644 index 00000000..f3130d45 --- /dev/null +++ b/expat/expat.h @@ -0,0 +1,1001 @@ +/* Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +#ifndef XmlParse_INCLUDED +#define XmlParse_INCLUDED 1 + +#ifdef __VMS +/* 0 1 2 3 0 1 2 3 + 1234567890123456789012345678901 1234567890123456789012345678901 */ +#define XML_SetProcessingInstructionHandler XML_SetProcessingInstrHandler +#define XML_SetUnparsedEntityDeclHandler XML_SetUnparsedEntDeclHandler +#define XML_SetStartNamespaceDeclHandler XML_SetStartNamespcDeclHandler +#define XML_SetExternalEntityRefHandlerArg XML_SetExternalEntRefHandlerArg +#endif + +#include + +#if defined(_MSC_EXTENSIONS) && !defined(__BEOS__) && !defined(__CYGWIN__) +#define XML_USE_MSC_EXTENSIONS 1 +#endif + +/* Expat tries very hard to make the API boundary very specifically + defined. There are two macros defined to control this boundary; + each of these can be defined before including this header to + achieve some different behavior, but doing so it not recommended or + tested frequently. + + XMLCALL - The calling convention to use for all calls across the + "library boundary." This will default to cdecl, and + try really hard to tell the compiler that's what we + want. + + XMLIMPORT - Whatever magic is needed to note that a function is + to be imported from a dynamically loaded library + (.dll, .so, or .sl, depending on your platform). + + The XMLCALL macro was added in Expat 1.95.7. The only one which is + expected to be directly useful in client code is XMLCALL. + + Note that on at least some Unix versions, the Expat library must be + compiled with the cdecl calling convention as the default since + system headers may assume the cdecl convention. +*/ +#ifndef XMLCALL +#if defined(XML_USE_MSC_EXTENSIONS) +#define XMLCALL __cdecl +#elif defined(__GNUC__) && defined(__i386) +#define XMLCALL __attribute__((cdecl)) +#else +/* For any platform which uses this definition and supports more than + one calling convention, we need to extend this definition to + declare the convention used on that platform, if it's possible to + do so. + + If this is the case for your platform, please file a bug report + with information on how to identify your platform via the C + pre-processor and how to specify the same calling convention as the + platform's malloc() implementation. +*/ +#define XMLCALL +#endif +#endif /* not defined XMLCALL */ + + +#if !defined(XML_STATIC) && !defined(XMLIMPORT) +#ifndef XML_BUILDING_EXPAT +/* using Expat from an application */ + +#ifdef XML_USE_MSC_EXTENSIONS +#define XMLIMPORT __declspec(dllimport) +#endif + +#endif +#endif /* not defined XML_STATIC */ + +/* If we didn't define it above, define it away: */ +#ifndef XMLIMPORT +#define XMLIMPORT +#endif + + +#define XMLPARSEAPI(type) XMLIMPORT type XMLCALL + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef XML_UNICODE_WCHAR_T +#define XML_UNICODE +#endif + +struct XML_ParserStruct; +typedef struct XML_ParserStruct *XML_Parser; + +#ifdef XML_UNICODE /* Information is UTF-16 encoded. */ +#ifdef XML_UNICODE_WCHAR_T +typedef wchar_t XML_Char; +typedef wchar_t XML_LChar; +#else +typedef unsigned short XML_Char; +typedef char XML_LChar; +#endif /* XML_UNICODE_WCHAR_T */ +#else /* Information is UTF-8 encoded. */ +typedef char XML_Char; +typedef char XML_LChar; +#endif /* XML_UNICODE */ + +/* Should this be defined using stdbool.h when C99 is available? */ +typedef unsigned char XML_Bool; +#define XML_TRUE ((XML_Bool) 1) +#define XML_FALSE ((XML_Bool) 0) + +/* The XML_Status enum gives the possible return values for several + API functions. The preprocessor #defines are included so this + stanza can be added to code that still needs to support older + versions of Expat 1.95.x: + + #ifndef XML_STATUS_OK + #define XML_STATUS_OK 1 + #define XML_STATUS_ERROR 0 + #endif + + Otherwise, the #define hackery is quite ugly and would have been + dropped. +*/ +enum XML_Status { + XML_STATUS_ERROR = 0, +#define XML_STATUS_ERROR XML_STATUS_ERROR + XML_STATUS_OK = 1 +#define XML_STATUS_OK XML_STATUS_OK +}; + +enum XML_Error { + XML_ERROR_NONE, + XML_ERROR_NO_MEMORY, + XML_ERROR_SYNTAX, + XML_ERROR_NO_ELEMENTS, + XML_ERROR_INVALID_TOKEN, + XML_ERROR_UNCLOSED_TOKEN, + XML_ERROR_PARTIAL_CHAR, + XML_ERROR_TAG_MISMATCH, + XML_ERROR_DUPLICATE_ATTRIBUTE, + XML_ERROR_JUNK_AFTER_DOC_ELEMENT, + XML_ERROR_PARAM_ENTITY_REF, + XML_ERROR_UNDEFINED_ENTITY, + XML_ERROR_RECURSIVE_ENTITY_REF, + XML_ERROR_ASYNC_ENTITY, + XML_ERROR_BAD_CHAR_REF, + XML_ERROR_BINARY_ENTITY_REF, + XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF, + XML_ERROR_MISPLACED_XML_PI, + XML_ERROR_UNKNOWN_ENCODING, + XML_ERROR_INCORRECT_ENCODING, + XML_ERROR_UNCLOSED_CDATA_SECTION, + XML_ERROR_EXTERNAL_ENTITY_HANDLING, + XML_ERROR_NOT_STANDALONE, + XML_ERROR_UNEXPECTED_STATE, + XML_ERROR_ENTITY_DECLARED_IN_PE, + XML_ERROR_FEATURE_REQUIRES_XML_DTD, + XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING, + XML_ERROR_UNBOUND_PREFIX +}; + +enum XML_Content_Type { + XML_CTYPE_EMPTY = 1, + XML_CTYPE_ANY, + XML_CTYPE_MIXED, + XML_CTYPE_NAME, + XML_CTYPE_CHOICE, + XML_CTYPE_SEQ +}; + +enum XML_Content_Quant { + XML_CQUANT_NONE, + XML_CQUANT_OPT, + XML_CQUANT_REP, + XML_CQUANT_PLUS +}; + +/* If type == XML_CTYPE_EMPTY or XML_CTYPE_ANY, then quant will be + XML_CQUANT_NONE, and the other fields will be zero or NULL. + If type == XML_CTYPE_MIXED, then quant will be NONE or REP and + numchildren will contain number of elements that may be mixed in + and children point to an array of XML_Content cells that will be + all of XML_CTYPE_NAME type with no quantification. + + If type == XML_CTYPE_NAME, then the name points to the name, and + the numchildren field will be zero and children will be NULL. The + quant fields indicates any quantifiers placed on the name. + + CHOICE and SEQ will have name NULL, the number of children in + numchildren and children will point, recursively, to an array + of XML_Content cells. + + The EMPTY, ANY, and MIXED types will only occur at top level. +*/ + +typedef struct XML_cp XML_Content; + +struct XML_cp { + enum XML_Content_Type type; + enum XML_Content_Quant quant; + XML_Char * name; + unsigned int numchildren; + XML_Content * children; +}; + + +/* This is called for an element declaration. See above for + description of the model argument. It's the caller's responsibility + to free model when finished with it. +*/ +typedef void (XMLCALL *XML_ElementDeclHandler) (void *userData, + const XML_Char *name, + XML_Content *model); + +XMLPARSEAPI(void) +XML_SetElementDeclHandler(XML_Parser parser, + XML_ElementDeclHandler eldecl); + +/* The Attlist declaration handler is called for *each* attribute. So + a single Attlist declaration with multiple attributes declared will + generate multiple calls to this handler. The "default" parameter + may be NULL in the case of the "#IMPLIED" or "#REQUIRED" + keyword. The "isrequired" parameter will be true and the default + value will be NULL in the case of "#REQUIRED". If "isrequired" is + true and default is non-NULL, then this is a "#FIXED" default. +*/ +typedef void (XMLCALL *XML_AttlistDeclHandler) ( + void *userData, + const XML_Char *elname, + const XML_Char *attname, + const XML_Char *att_type, + const XML_Char *dflt, + int isrequired); + +XMLPARSEAPI(void) +XML_SetAttlistDeclHandler(XML_Parser parser, + XML_AttlistDeclHandler attdecl); + +/* The XML declaration handler is called for *both* XML declarations + and text declarations. The way to distinguish is that the version + parameter will be NULL for text declarations. The encoding + parameter may be NULL for XML declarations. The standalone + parameter will be -1, 0, or 1 indicating respectively that there + was no standalone parameter in the declaration, that it was given + as no, or that it was given as yes. +*/ +typedef void (XMLCALL *XML_XmlDeclHandler) (void *userData, + const XML_Char *version, + const XML_Char *encoding, + int standalone); + +XMLPARSEAPI(void) +XML_SetXmlDeclHandler(XML_Parser parser, + XML_XmlDeclHandler xmldecl); + + +typedef struct { + void *(XMLCALL *malloc_fcn)(size_t size); + void *(XMLCALL *realloc_fcn)(void *ptr, size_t size); + void (XMLCALL *free_fcn)(void *ptr); +} XML_Memory_Handling_Suite; + +/* Constructs a new parser; encoding is the encoding specified by the + external protocol or NULL if there is none specified. +*/ +XMLPARSEAPI(XML_Parser) +XML_ParserCreate(const XML_Char *encoding); + +/* Constructs a new parser and namespace processor. Element type + names and attribute names that belong to a namespace will be + expanded; unprefixed attribute names are never expanded; unprefixed + element type names are expanded only if there is a default + namespace. The expanded name is the concatenation of the namespace + URI, the namespace separator character, and the local part of the + name. If the namespace separator is '\0' then the namespace URI + and the local part will be concatenated without any separator. + When a namespace is not declared, the name and prefix will be + passed through without expansion. +*/ +XMLPARSEAPI(XML_Parser) +XML_ParserCreateNS(const XML_Char *encoding, XML_Char namespaceSeparator); + + +/* Constructs a new parser using the memory management suite referred to + by memsuite. If memsuite is NULL, then use the standard library memory + suite. If namespaceSeparator is non-NULL it creates a parser with + namespace processing as described above. The character pointed at + will serve as the namespace separator. + + All further memory operations used for the created parser will come from + the given suite. +*/ +XMLPARSEAPI(XML_Parser) +XML_ParserCreate_MM(const XML_Char *encoding, + const XML_Memory_Handling_Suite *memsuite, + const XML_Char *namespaceSeparator); + +/* Prepare a parser object to be re-used. This is particularly + valuable when memory allocation overhead is disproportionatly high, + such as when a large number of small documnents need to be parsed. + All handlers are cleared from the parser, except for the + unknownEncodingHandler. The parser's external state is re-initialized + except for the values of ns and ns_triplets. + + Added in Expat 1.95.3. +*/ +XMLPARSEAPI(XML_Bool) +XML_ParserReset(XML_Parser parser, const XML_Char *encoding); + +/* atts is array of name/value pairs, terminated by 0; + names and values are 0 terminated. +*/ +typedef void (XMLCALL *XML_StartElementHandler) (void *userData, + const XML_Char *name, + const XML_Char **atts); + +typedef void (XMLCALL *XML_EndElementHandler) (void *userData, + const XML_Char *name); + + +/* s is not 0 terminated. */ +typedef void (XMLCALL *XML_CharacterDataHandler) (void *userData, + const XML_Char *s, + int len); + +/* target and data are 0 terminated */ +typedef void (XMLCALL *XML_ProcessingInstructionHandler) ( + void *userData, + const XML_Char *target, + const XML_Char *data); + +/* data is 0 terminated */ +typedef void (XMLCALL *XML_CommentHandler) (void *userData, + const XML_Char *data); + +typedef void (XMLCALL *XML_StartCdataSectionHandler) (void *userData); +typedef void (XMLCALL *XML_EndCdataSectionHandler) (void *userData); + +/* This is called for any characters in the XML document for which + there is no applicable handler. This includes both characters that + are part of markup which is of a kind that is not reported + (comments, markup declarations), or characters that are part of a + construct which could be reported but for which no handler has been + supplied. The characters are passed exactly as they were in the XML + document except that they will be encoded in UTF-8 or UTF-16. + Line boundaries are not normalized. Note that a byte order mark + character is not passed to the default handler. There are no + guarantees about how characters are divided between calls to the + default handler: for example, a comment might be split between + multiple calls. +*/ +typedef void (XMLCALL *XML_DefaultHandler) (void *userData, + const XML_Char *s, + int len); + +/* This is called for the start of the DOCTYPE declaration, before + any DTD or internal subset is parsed. +*/ +typedef void (XMLCALL *XML_StartDoctypeDeclHandler) ( + void *userData, + const XML_Char *doctypeName, + const XML_Char *sysid, + const XML_Char *pubid, + int has_internal_subset); + +/* This is called for the start of the DOCTYPE declaration when the + closing > is encountered, but after processing any external + subset. +*/ +typedef void (XMLCALL *XML_EndDoctypeDeclHandler)(void *userData); + +/* This is called for entity declarations. The is_parameter_entity + argument will be non-zero if the entity is a parameter entity, zero + otherwise. + + For internal entities (), value will + be non-NULL and systemId, publicID, and notationName will be NULL. + The value string is NOT nul-terminated; the length is provided in + the value_length argument. Since it is legal to have zero-length + values, do not use this argument to test for internal entities. + + For external entities, value will be NULL and systemId will be + non-NULL. The publicId argument will be NULL unless a public + identifier was provided. The notationName argument will have a + non-NULL value only for unparsed entity declarations. + + Note that is_parameter_entity can't be changed to XML_Bool, since + that would break binary compatibility. +*/ +typedef void (XMLCALL *XML_EntityDeclHandler) ( + void *userData, + const XML_Char *entityName, + int is_parameter_entity, + const XML_Char *value, + int value_length, + const XML_Char *base, + const XML_Char *systemId, + const XML_Char *publicId, + const XML_Char *notationName); + +XMLPARSEAPI(void) +XML_SetEntityDeclHandler(XML_Parser parser, + XML_EntityDeclHandler handler); + +/* OBSOLETE -- OBSOLETE -- OBSOLETE + This handler has been superceded by the EntityDeclHandler above. + It is provided here for backward compatibility. + + This is called for a declaration of an unparsed (NDATA) entity. + The base argument is whatever was set by XML_SetBase. The + entityName, systemId and notationName arguments will never be + NULL. The other arguments may be. +*/ +typedef void (XMLCALL *XML_UnparsedEntityDeclHandler) ( + void *userData, + const XML_Char *entityName, + const XML_Char *base, + const XML_Char *systemId, + const XML_Char *publicId, + const XML_Char *notationName); + +/* This is called for a declaration of notation. The base argument is + whatever was set by XML_SetBase. The notationName will never be + NULL. The other arguments can be. +*/ +typedef void (XMLCALL *XML_NotationDeclHandler) ( + void *userData, + const XML_Char *notationName, + const XML_Char *base, + const XML_Char *systemId, + const XML_Char *publicId); + +/* When namespace processing is enabled, these are called once for + each namespace declaration. The call to the start and end element + handlers occur between the calls to the start and end namespace + declaration handlers. For an xmlns attribute, prefix will be + NULL. For an xmlns="" attribute, uri will be NULL. +*/ +typedef void (XMLCALL *XML_StartNamespaceDeclHandler) ( + void *userData, + const XML_Char *prefix, + const XML_Char *uri); + +typedef void (XMLCALL *XML_EndNamespaceDeclHandler) ( + void *userData, + const XML_Char *prefix); + +/* This is called if the document is not standalone, that is, it has an + external subset or a reference to a parameter entity, but does not + have standalone="yes". If this handler returns XML_STATUS_ERROR, + then processing will not continue, and the parser will return a + XML_ERROR_NOT_STANDALONE error. + If parameter entity parsing is enabled, then in addition to the + conditions above this handler will only be called if the referenced + entity was actually read. +*/ +typedef int (XMLCALL *XML_NotStandaloneHandler) (void *userData); + +/* This is called for a reference to an external parsed general + entity. The referenced entity is not automatically parsed. The + application can parse it immediately or later using + XML_ExternalEntityParserCreate. + + The parser argument is the parser parsing the entity containing the + reference; it can be passed as the parser argument to + XML_ExternalEntityParserCreate. The systemId argument is the + system identifier as specified in the entity declaration; it will + not be NULL. + + The base argument is the system identifier that should be used as + the base for resolving systemId if systemId was relative; this is + set by XML_SetBase; it may be NULL. + + The publicId argument is the public identifier as specified in the + entity declaration, or NULL if none was specified; the whitespace + in the public identifier will have been normalized as required by + the XML spec. + + The context argument specifies the parsing context in the format + expected by the context argument to XML_ExternalEntityParserCreate; + context is valid only until the handler returns, so if the + referenced entity is to be parsed later, it must be copied. + context is NULL only when the entity is a parameter entity. + + The handler should return XML_STATUS_ERROR if processing should not + continue because of a fatal error in the handling of the external + entity. In this case the calling parser will return an + XML_ERROR_EXTERNAL_ENTITY_HANDLING error. + + Note that unlike other handlers the first argument is the parser, + not userData. +*/ +typedef int (XMLCALL *XML_ExternalEntityRefHandler) ( + XML_Parser parser, + const XML_Char *context, + const XML_Char *base, + const XML_Char *systemId, + const XML_Char *publicId); + +/* This is called in two situations: + 1) An entity reference is encountered for which no declaration + has been read *and* this is not an error. + 2) An internal entity reference is read, but not expanded, because + XML_SetDefaultHandler has been called. + Note: skipped parameter entities in declarations and skipped general + entities in attribute values cannot be reported, because + the event would be out of sync with the reporting of the + declarations or attribute values +*/ +typedef void (XMLCALL *XML_SkippedEntityHandler) ( + void *userData, + const XML_Char *entityName, + int is_parameter_entity); + +/* This structure is filled in by the XML_UnknownEncodingHandler to + provide information to the parser about encodings that are unknown + to the parser. + + The map[b] member gives information about byte sequences whose + first byte is b. + + If map[b] is c where c is >= 0, then b by itself encodes the + Unicode scalar value c. + + If map[b] is -1, then the byte sequence is malformed. + + If map[b] is -n, where n >= 2, then b is the first byte of an + n-byte sequence that encodes a single Unicode scalar value. + + The data member will be passed as the first argument to the convert + function. + + The convert function is used to convert multibyte sequences; s will + point to a n-byte sequence where map[(unsigned char)*s] == -n. The + convert function must return the Unicode scalar value represented + by this byte sequence or -1 if the byte sequence is malformed. + + The convert function may be NULL if the encoding is a single-byte + encoding, that is if map[b] >= -1 for all bytes b. + + When the parser is finished with the encoding, then if release is + not NULL, it will call release passing it the data member; once + release has been called, the convert function will not be called + again. + + Expat places certain restrictions on the encodings that are supported + using this mechanism. + + 1. Every ASCII character that can appear in a well-formed XML document, + other than the characters + + $@\^`{}~ + + must be represented by a single byte, and that byte must be the + same byte that represents that character in ASCII. + + 2. No character may require more than 4 bytes to encode. + + 3. All characters encoded must have Unicode scalar values <= + 0xFFFF, (i.e., characters that would be encoded by surrogates in + UTF-16 are not allowed). Note that this restriction doesn't + apply to the built-in support for UTF-8 and UTF-16. + + 4. No Unicode character may be encoded by more than one distinct + sequence of bytes. +*/ +typedef struct { + int map[256]; + void *data; + int (XMLCALL *convert)(void *data, const char *s); + void (XMLCALL *release)(void *data); +} XML_Encoding; + +/* This is called for an encoding that is unknown to the parser. + + The encodingHandlerData argument is that which was passed as the + second argument to XML_SetUnknownEncodingHandler. + + The name argument gives the name of the encoding as specified in + the encoding declaration. + + If the callback can provide information about the encoding, it must + fill in the XML_Encoding structure, and return XML_STATUS_OK. + Otherwise it must return XML_STATUS_ERROR. + + If info does not describe a suitable encoding, then the parser will + return an XML_UNKNOWN_ENCODING error. +*/ +typedef int (XMLCALL *XML_UnknownEncodingHandler) ( + void *encodingHandlerData, + const XML_Char *name, + XML_Encoding *info); + +XMLPARSEAPI(void) +XML_SetElementHandler(XML_Parser parser, + XML_StartElementHandler start, + XML_EndElementHandler end); + +XMLPARSEAPI(void) +XML_SetStartElementHandler(XML_Parser, XML_StartElementHandler); + +XMLPARSEAPI(void) +XML_SetEndElementHandler(XML_Parser, XML_EndElementHandler); + +XMLPARSEAPI(void) +XML_SetCharacterDataHandler(XML_Parser parser, + XML_CharacterDataHandler handler); + +XMLPARSEAPI(void) +XML_SetProcessingInstructionHandler(XML_Parser parser, + XML_ProcessingInstructionHandler handler); +XMLPARSEAPI(void) +XML_SetCommentHandler(XML_Parser parser, + XML_CommentHandler handler); + +XMLPARSEAPI(void) +XML_SetCdataSectionHandler(XML_Parser parser, + XML_StartCdataSectionHandler start, + XML_EndCdataSectionHandler end); + +XMLPARSEAPI(void) +XML_SetStartCdataSectionHandler(XML_Parser parser, + XML_StartCdataSectionHandler start); + +XMLPARSEAPI(void) +XML_SetEndCdataSectionHandler(XML_Parser parser, + XML_EndCdataSectionHandler end); + +/* This sets the default handler and also inhibits expansion of + internal entities. These entity references will be passed to the + default handler, or to the skipped entity handler, if one is set. +*/ +XMLPARSEAPI(void) +XML_SetDefaultHandler(XML_Parser parser, + XML_DefaultHandler handler); + +/* This sets the default handler but does not inhibit expansion of + internal entities. The entity reference will not be passed to the + default handler. +*/ +XMLPARSEAPI(void) +XML_SetDefaultHandlerExpand(XML_Parser parser, + XML_DefaultHandler handler); + +XMLPARSEAPI(void) +XML_SetDoctypeDeclHandler(XML_Parser parser, + XML_StartDoctypeDeclHandler start, + XML_EndDoctypeDeclHandler end); + +XMLPARSEAPI(void) +XML_SetStartDoctypeDeclHandler(XML_Parser parser, + XML_StartDoctypeDeclHandler start); + +XMLPARSEAPI(void) +XML_SetEndDoctypeDeclHandler(XML_Parser parser, + XML_EndDoctypeDeclHandler end); + +XMLPARSEAPI(void) +XML_SetUnparsedEntityDeclHandler(XML_Parser parser, + XML_UnparsedEntityDeclHandler handler); + +XMLPARSEAPI(void) +XML_SetNotationDeclHandler(XML_Parser parser, + XML_NotationDeclHandler handler); + +XMLPARSEAPI(void) +XML_SetNamespaceDeclHandler(XML_Parser parser, + XML_StartNamespaceDeclHandler start, + XML_EndNamespaceDeclHandler end); + +XMLPARSEAPI(void) +XML_SetStartNamespaceDeclHandler(XML_Parser parser, + XML_StartNamespaceDeclHandler start); + +XMLPARSEAPI(void) +XML_SetEndNamespaceDeclHandler(XML_Parser parser, + XML_EndNamespaceDeclHandler end); + +XMLPARSEAPI(void) +XML_SetNotStandaloneHandler(XML_Parser parser, + XML_NotStandaloneHandler handler); + +XMLPARSEAPI(void) +XML_SetExternalEntityRefHandler(XML_Parser parser, + XML_ExternalEntityRefHandler handler); + +/* If a non-NULL value for arg is specified here, then it will be + passed as the first argument to the external entity ref handler + instead of the parser object. +*/ +XMLPARSEAPI(void) +XML_SetExternalEntityRefHandlerArg(XML_Parser, void *arg); + +XMLPARSEAPI(void) +XML_SetSkippedEntityHandler(XML_Parser parser, + XML_SkippedEntityHandler handler); + +XMLPARSEAPI(void) +XML_SetUnknownEncodingHandler(XML_Parser parser, + XML_UnknownEncodingHandler handler, + void *encodingHandlerData); + +/* This can be called within a handler for a start element, end + element, processing instruction or character data. It causes the + corresponding markup to be passed to the default handler. +*/ +XMLPARSEAPI(void) +XML_DefaultCurrent(XML_Parser parser); + +/* If do_nst is non-zero, and namespace processing is in effect, and + a name has a prefix (i.e. an explicit namespace qualifier) then + that name is returned as a triplet in a single string separated by + the separator character specified when the parser was created: URI + + sep + local_name + sep + prefix. + + If do_nst is zero, then namespace information is returned in the + default manner (URI + sep + local_name) whether or not the name + has a prefix. + + Note: Calling XML_SetReturnNSTriplet after XML_Parse or + XML_ParseBuffer has no effect. +*/ + +XMLPARSEAPI(void) +XML_SetReturnNSTriplet(XML_Parser parser, int do_nst); + +/* This value is passed as the userData argument to callbacks. */ +XMLPARSEAPI(void) +XML_SetUserData(XML_Parser parser, void *userData); + +/* Returns the last value set by XML_SetUserData or NULL. */ +#define XML_GetUserData(parser) (*(void **)(parser)) + +/* This is equivalent to supplying an encoding argument to + XML_ParserCreate. On success XML_SetEncoding returns non-zero, + zero otherwise. + Note: Calling XML_SetEncoding after XML_Parse or XML_ParseBuffer + has no effect and returns XML_STATUS_ERROR. +*/ +XMLPARSEAPI(enum XML_Status) +XML_SetEncoding(XML_Parser parser, const XML_Char *encoding); + +/* If this function is called, then the parser will be passed as the + first argument to callbacks instead of userData. The userData will + still be accessible using XML_GetUserData. +*/ +XMLPARSEAPI(void) +XML_UseParserAsHandlerArg(XML_Parser parser); + +/* If useDTD == XML_TRUE is passed to this function, then the parser + will assume that there is an external subset, even if none is + specified in the document. In such a case the parser will call the + externalEntityRefHandler with a value of NULL for the systemId + argument (the publicId and context arguments will be NULL as well). + Note: If this function is called, then this must be done before + the first call to XML_Parse or XML_ParseBuffer, since it will + have no effect after that. Returns + XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING. + Note: If the document does not have a DOCTYPE declaration at all, + then startDoctypeDeclHandler and endDoctypeDeclHandler will not + be called, despite an external subset being parsed. + Note: If XML_DTD is not defined when Expat is compiled, returns + XML_ERROR_FEATURE_REQUIRES_XML_DTD. +*/ +XMLPARSEAPI(enum XML_Error) +XML_UseForeignDTD(XML_Parser parser, XML_Bool useDTD); + + +/* Sets the base to be used for resolving relative URIs in system + identifiers in declarations. Resolving relative identifiers is + left to the application: this value will be passed through as the + base argument to the XML_ExternalEntityRefHandler, + XML_NotationDeclHandler and XML_UnparsedEntityDeclHandler. The base + argument will be copied. Returns XML_STATUS_ERROR if out of memory, + XML_STATUS_OK otherwise. +*/ +XMLPARSEAPI(enum XML_Status) +XML_SetBase(XML_Parser parser, const XML_Char *base); + +XMLPARSEAPI(const XML_Char *) +XML_GetBase(XML_Parser parser); + +/* Returns the number of the attribute/value pairs passed in last call + to the XML_StartElementHandler that were specified in the start-tag + rather than defaulted. Each attribute/value pair counts as 2; thus + this correspondds to an index into the atts array passed to the + XML_StartElementHandler. +*/ +XMLPARSEAPI(int) +XML_GetSpecifiedAttributeCount(XML_Parser parser); + +/* Returns the index of the ID attribute passed in the last call to + XML_StartElementHandler, or -1 if there is no ID attribute. Each + attribute/value pair counts as 2; thus this correspondds to an + index into the atts array passed to the XML_StartElementHandler. +*/ +XMLPARSEAPI(int) +XML_GetIdAttributeIndex(XML_Parser parser); + +/* Parses some input. Returns XML_STATUS_ERROR if a fatal error is + detected. The last call to XML_Parse must have isFinal true; len + may be zero for this call (or any other). + + Though the return values for these functions has always been + described as a Boolean value, the implementation, at least for the + 1.95.x series, has always returned exactly one of the XML_Status + values. +*/ +XMLPARSEAPI(enum XML_Status) +XML_Parse(XML_Parser parser, const char *s, int len, int isFinal); + +XMLPARSEAPI(void *) +XML_GetBuffer(XML_Parser parser, int len); + +XMLPARSEAPI(enum XML_Status) +XML_ParseBuffer(XML_Parser parser, int len, int isFinal); + +/* Creates an XML_Parser object that can parse an external general + entity; context is a '\0'-terminated string specifying the parse + context; encoding is a '\0'-terminated string giving the name of + the externally specified encoding, or NULL if there is no + externally specified encoding. The context string consists of a + sequence of tokens separated by formfeeds (\f); a token consisting + of a name specifies that the general entity of the name is open; a + token of the form prefix=uri specifies the namespace for a + particular prefix; a token of the form =uri specifies the default + namespace. This can be called at any point after the first call to + an ExternalEntityRefHandler so longer as the parser has not yet + been freed. The new parser is completely independent and may + safely be used in a separate thread. The handlers and userData are + initialized from the parser argument. Returns NULL if out of memory. + Otherwise returns a new XML_Parser object. +*/ +XMLPARSEAPI(XML_Parser) +XML_ExternalEntityParserCreate(XML_Parser parser, + const XML_Char *context, + const XML_Char *encoding); + +enum XML_ParamEntityParsing { + XML_PARAM_ENTITY_PARSING_NEVER, + XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE, + XML_PARAM_ENTITY_PARSING_ALWAYS +}; + +/* Controls parsing of parameter entities (including the external DTD + subset). If parsing of parameter entities is enabled, then + references to external parameter entities (including the external + DTD subset) will be passed to the handler set with + XML_SetExternalEntityRefHandler. The context passed will be 0. + + Unlike external general entities, external parameter entities can + only be parsed synchronously. If the external parameter entity is + to be parsed, it must be parsed during the call to the external + entity ref handler: the complete sequence of + XML_ExternalEntityParserCreate, XML_Parse/XML_ParseBuffer and + XML_ParserFree calls must be made during this call. After + XML_ExternalEntityParserCreate has been called to create the parser + for the external parameter entity (context must be 0 for this + call), it is illegal to make any calls on the old parser until + XML_ParserFree has been called on the newly created parser. + If the library has been compiled without support for parameter + entity parsing (ie without XML_DTD being defined), then + XML_SetParamEntityParsing will return 0 if parsing of parameter + entities is requested; otherwise it will return non-zero. + Note: If XML_SetParamEntityParsing is called after XML_Parse or + XML_ParseBuffer, then it has no effect and will always return 0. +*/ +XMLPARSEAPI(int) +XML_SetParamEntityParsing(XML_Parser parser, + enum XML_ParamEntityParsing parsing); + +/* If XML_Parse or XML_ParseBuffer have returned XML_STATUS_ERROR, then + XML_GetErrorCode returns information about the error. +*/ +XMLPARSEAPI(enum XML_Error) +XML_GetErrorCode(XML_Parser parser); + +/* These functions return information about the current parse + location. They may be called from any callback called to report + some parse event; in this case the location is the location of the + first of the sequence of characters that generated the event. When + called from callbacks generated by declarations in the document + prologue, the location identified isn't as neatly defined, but will + be within the relevant markup. When called outside of the callback + functions, the position indicated will be just past the last parse + event (regardless of whether there was an associated callback). + + They may also be called after returning from a call to XML_Parse + or XML_ParseBuffer. If the return value is XML_STATUS_ERROR then + the location is the location of the character at which the error + was detected; otherwise the location is the location of the last + parse event, as described above. +*/ +XMLPARSEAPI(int) XML_GetCurrentLineNumber(XML_Parser parser); +XMLPARSEAPI(int) XML_GetCurrentColumnNumber(XML_Parser parser); +XMLPARSEAPI(long) XML_GetCurrentByteIndex(XML_Parser parser); + +/* Return the number of bytes in the current event. + Returns 0 if the event is in an internal entity. +*/ +XMLPARSEAPI(int) +XML_GetCurrentByteCount(XML_Parser parser); + +/* If XML_CONTEXT_BYTES is defined, returns the input buffer, sets + the integer pointed to by offset to the offset within this buffer + of the current parse position, and sets the integer pointed to by size + to the size of this buffer (the number of input bytes). Otherwise + returns a NULL pointer. Also returns a NULL pointer if a parse isn't + active. + + NOTE: The character pointer returned should not be used outside + the handler that makes the call. +*/ +XMLPARSEAPI(const char *) +XML_GetInputContext(XML_Parser parser, + int *offset, + int *size); + +/* For backwards compatibility with previous versions. */ +#define XML_GetErrorLineNumber XML_GetCurrentLineNumber +#define XML_GetErrorColumnNumber XML_GetCurrentColumnNumber +#define XML_GetErrorByteIndex XML_GetCurrentByteIndex + +/* Frees the content model passed to the element declaration handler */ +XMLPARSEAPI(void) +XML_FreeContentModel(XML_Parser parser, XML_Content *model); + +/* Exposing the memory handling functions used in Expat */ +XMLPARSEAPI(void *) +XML_MemMalloc(XML_Parser parser, size_t size); + +XMLPARSEAPI(void *) +XML_MemRealloc(XML_Parser parser, void *ptr, size_t size); + +XMLPARSEAPI(void) +XML_MemFree(XML_Parser parser, void *ptr); + +/* Frees memory used by the parser. */ +XMLPARSEAPI(void) +XML_ParserFree(XML_Parser parser); + +/* Returns a string describing the error. */ +XMLPARSEAPI(const XML_LChar *) +XML_ErrorString(enum XML_Error code); + +/* Return a string containing the version number of this expat */ +XMLPARSEAPI(const XML_LChar *) +XML_ExpatVersion(void); + +typedef struct { + int major; + int minor; + int micro; +} XML_Expat_Version; + +/* Return an XML_Expat_Version structure containing numeric version + number information for this version of expat. +*/ +XMLPARSEAPI(XML_Expat_Version) +XML_ExpatVersionInfo(void); + +/* Added in Expat 1.95.5. */ +enum XML_FeatureEnum { + XML_FEATURE_END = 0, + XML_FEATURE_UNICODE, + XML_FEATURE_UNICODE_WCHAR_T, + XML_FEATURE_DTD, + XML_FEATURE_CONTEXT_BYTES, + XML_FEATURE_MIN_SIZE, + XML_FEATURE_SIZEOF_XML_CHAR, + XML_FEATURE_SIZEOF_XML_LCHAR + /* Additional features must be added to the end of this enum. */ +}; + +typedef struct { + enum XML_FeatureEnum feature; + const XML_LChar *name; + long int value; +} XML_Feature; + +XMLPARSEAPI(const XML_Feature *) +XML_GetFeatureList(void); + + +/* Expat follows the GNU/Linux convention of odd number minor version for + beta/development releases and even number minor version for stable + releases. Micro is bumped with each release, and set to 0 with each + change to major or minor version. +*/ +#define XML_MAJOR_VERSION 1 +#define XML_MINOR_VERSION 95 +#define XML_MICRO_VERSION 7 + +#ifdef __cplusplus +} +#endif + +#endif /* not XmlParse_INCLUDED */ diff --git a/expat/expat_config.h b/expat/expat_config.h new file mode 100644 index 00000000..8692b422 --- /dev/null +++ b/expat/expat_config.h @@ -0,0 +1,15 @@ +#ifndef EXPAT_CONFIG_H +#define EXPAT_CONFIG_H 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if defined(WIN32) && !defined(__MINGW32__) +#include "winconfig.h" +#else +#define XML_DTD 1 +#define XML_MIN_SIZE 1 +#endif + +#endif diff --git a/expat/iasciitab.h b/expat/iasciitab.h new file mode 100644 index 00000000..24a1d5cc --- /dev/null +++ b/expat/iasciitab.h @@ -0,0 +1,37 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +/* Like asciitab.h, except that 0xD has code BT_S rather than BT_CR */ +/* 0x00 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x04 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x08 */ BT_NONXML, BT_S, BT_LF, BT_NONXML, +/* 0x0C */ BT_NONXML, BT_S, BT_NONXML, BT_NONXML, +/* 0x10 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x14 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x18 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x1C */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0x20 */ BT_S, BT_EXCL, BT_QUOT, BT_NUM, +/* 0x24 */ BT_OTHER, BT_PERCNT, BT_AMP, BT_APOS, +/* 0x28 */ BT_LPAR, BT_RPAR, BT_AST, BT_PLUS, +/* 0x2C */ BT_COMMA, BT_MINUS, BT_NAME, BT_SOL, +/* 0x30 */ BT_DIGIT, BT_DIGIT, BT_DIGIT, BT_DIGIT, +/* 0x34 */ BT_DIGIT, BT_DIGIT, BT_DIGIT, BT_DIGIT, +/* 0x38 */ BT_DIGIT, BT_DIGIT, BT_COLON, BT_SEMI, +/* 0x3C */ BT_LT, BT_EQUALS, BT_GT, BT_QUEST, +/* 0x40 */ BT_OTHER, BT_HEX, BT_HEX, BT_HEX, +/* 0x44 */ BT_HEX, BT_HEX, BT_HEX, BT_NMSTRT, +/* 0x48 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x4C */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x50 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x54 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x58 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_LSQB, +/* 0x5C */ BT_OTHER, BT_RSQB, BT_OTHER, BT_NMSTRT, +/* 0x60 */ BT_OTHER, BT_HEX, BT_HEX, BT_HEX, +/* 0x64 */ BT_HEX, BT_HEX, BT_HEX, BT_NMSTRT, +/* 0x68 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x6C */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x70 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x74 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0x78 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_OTHER, +/* 0x7C */ BT_VERBAR, BT_OTHER, BT_OTHER, BT_OTHER, diff --git a/expat/internal.h b/expat/internal.h new file mode 100644 index 00000000..ff056c65 --- /dev/null +++ b/expat/internal.h @@ -0,0 +1,73 @@ +/* internal.h + + Internal definitions used by Expat. This is not needed to compile + client code. + + The following calling convention macros are defined for frequently + called functions: + + FASTCALL - Used for those internal functions that have a simple + body and a low number of arguments and local variables. + + PTRCALL - Used for functions called though function pointers. + + PTRFASTCALL - Like PTRCALL, but for low number of arguments. + + inline - Used for selected internal functions for which inlining + may improve performance on some platforms. + + Note: Use of these macros is based on judgement, not hard rules, + and therefore subject to change. +*/ + +#if defined(__GNUC__) && defined(__i386__) +/* We'll use this version by default only where we know it helps. + + regparm() generates warnings on Solaris boxes. See SF bug #692878. + + Instability reported with egcs on a RedHat Linux 7.3. + Let's comment out: + #define FASTCALL __attribute__((stdcall, regparm(3))) + and let's try this: +*/ +#define FASTCALL __attribute__((regparm(3))) +#define PTRFASTCALL __attribute__((regparm(3))) +#endif + +/* Using __fastcall seems to have an unexpected negative effect under + MS VC++, especially for function pointers, so we won't use it for + now on that platform. It may be reconsidered for a future release + if it can be made more effective. + Likely reason: __fastcall on Windows is like stdcall, therefore + the compiler cannot perform stack optimizations for call clusters. +*/ + +/* Make sure all of these are defined if they aren't already. */ + +#ifndef FASTCALL +#define FASTCALL +#endif + +#ifndef PTRCALL +#define PTRCALL +#endif + +#ifndef PTRFASTCALL +#define PTRFASTCALL +#endif + +#ifndef XML_MIN_SIZE +#if !defined(__cplusplus) && !defined(inline) +#ifdef __GNUC__ +#define inline __inline +#endif /* __GNUC__ */ +#endif +#endif /* XML_MIN_SIZE */ + +#ifdef __cplusplus +#define inline inline +#else +#ifndef inline +#define inline +#endif +#endif diff --git a/expat/latin1tab.h b/expat/latin1tab.h new file mode 100644 index 00000000..53c25d76 --- /dev/null +++ b/expat/latin1tab.h @@ -0,0 +1,36 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +/* 0x80 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0x84 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0x88 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0x8C */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0x90 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0x94 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0x98 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0x9C */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0xA0 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0xA4 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0xA8 */ BT_OTHER, BT_OTHER, BT_NMSTRT, BT_OTHER, +/* 0xAC */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0xB0 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0xB4 */ BT_OTHER, BT_NMSTRT, BT_OTHER, BT_NAME, +/* 0xB8 */ BT_OTHER, BT_OTHER, BT_NMSTRT, BT_OTHER, +/* 0xBC */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER, +/* 0xC0 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xC4 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xC8 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xCC */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xD0 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xD4 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_OTHER, +/* 0xD8 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xDC */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xE0 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xE4 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xE8 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xEC */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xF0 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xF4 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_OTHER, +/* 0xF8 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, +/* 0xFC */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, diff --git a/expat/nametab.h b/expat/nametab.h new file mode 100644 index 00000000..b05e62c7 --- /dev/null +++ b/expat/nametab.h @@ -0,0 +1,150 @@ +static const unsigned namingBitmap[] = { +0x00000000, 0x00000000, 0x00000000, 0x00000000, +0x00000000, 0x00000000, 0x00000000, 0x00000000, +0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, +0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, +0x00000000, 0x04000000, 0x87FFFFFE, 0x07FFFFFE, +0x00000000, 0x00000000, 0xFF7FFFFF, 0xFF7FFFFF, +0xFFFFFFFF, 0x7FF3FFFF, 0xFFFFFDFE, 0x7FFFFFFF, +0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE00F, 0xFC31FFFF, +0x00FFFFFF, 0x00000000, 0xFFFF0000, 0xFFFFFFFF, +0xFFFFFFFF, 0xF80001FF, 0x00000003, 0x00000000, +0x00000000, 0x00000000, 0x00000000, 0x00000000, +0xFFFFD740, 0xFFFFFFFB, 0x547F7FFF, 0x000FFFFD, +0xFFFFDFFE, 0xFFFFFFFF, 0xDFFEFFFF, 0xFFFFFFFF, +0xFFFF0003, 0xFFFFFFFF, 0xFFFF199F, 0x033FCFFF, +0x00000000, 0xFFFE0000, 0x027FFFFF, 0xFFFFFFFE, +0x0000007F, 0x00000000, 0xFFFF0000, 0x000707FF, +0x00000000, 0x07FFFFFE, 0x000007FE, 0xFFFE0000, +0xFFFFFFFF, 0x7CFFFFFF, 0x002F7FFF, 0x00000060, +0xFFFFFFE0, 0x23FFFFFF, 0xFF000000, 0x00000003, +0xFFF99FE0, 0x03C5FDFF, 0xB0000000, 0x00030003, +0xFFF987E0, 0x036DFDFF, 0x5E000000, 0x001C0000, +0xFFFBAFE0, 0x23EDFDFF, 0x00000000, 0x00000001, +0xFFF99FE0, 0x23CDFDFF, 0xB0000000, 0x00000003, +0xD63DC7E0, 0x03BFC718, 0x00000000, 0x00000000, +0xFFFDDFE0, 0x03EFFDFF, 0x00000000, 0x00000003, +0xFFFDDFE0, 0x03EFFDFF, 0x40000000, 0x00000003, +0xFFFDDFE0, 0x03FFFDFF, 0x00000000, 0x00000003, +0x00000000, 0x00000000, 0x00000000, 0x00000000, +0xFFFFFFFE, 0x000D7FFF, 0x0000003F, 0x00000000, +0xFEF02596, 0x200D6CAE, 0x0000001F, 0x00000000, +0x00000000, 0x00000000, 0xFFFFFEFF, 0x000003FF, +0x00000000, 0x00000000, 0x00000000, 0x00000000, +0x00000000, 0x00000000, 0x00000000, 0x00000000, +0x00000000, 0xFFFFFFFF, 0xFFFF003F, 0x007FFFFF, +0x0007DAED, 0x50000000, 0x82315001, 0x002C62AB, +0x40000000, 0xF580C900, 0x00000007, 0x02010800, +0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, +0x0FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x03FFFFFF, +0x3F3FFFFF, 0xFFFFFFFF, 0xAAFF3F3F, 0x3FFFFFFF, +0xFFFFFFFF, 0x5FDFFFFF, 0x0FCF1FDC, 0x1FDC1FFF, +0x00000000, 0x00004C40, 0x00000000, 0x00000000, +0x00000007, 0x00000000, 0x00000000, 0x00000000, +0x00000080, 0x000003FE, 0xFFFFFFFE, 0xFFFFFFFF, +0x001FFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0x07FFFFFF, +0xFFFFFFE0, 0x00001FFF, 0x00000000, 0x00000000, +0x00000000, 0x00000000, 0x00000000, 0x00000000, +0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, +0xFFFFFFFF, 0x0000003F, 0x00000000, 0x00000000, +0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, +0xFFFFFFFF, 0x0000000F, 0x00000000, 0x00000000, +0x00000000, 0x07FF6000, 0x87FFFFFE, 0x07FFFFFE, +0x00000000, 0x00800000, 0xFF7FFFFF, 0xFF7FFFFF, +0x00FFFFFF, 0x00000000, 0xFFFF0000, 0xFFFFFFFF, +0xFFFFFFFF, 0xF80001FF, 0x00030003, 0x00000000, +0xFFFFFFFF, 0xFFFFFFFF, 0x0000003F, 0x00000003, +0xFFFFD7C0, 0xFFFFFFFB, 0x547F7FFF, 0x000FFFFD, +0xFFFFDFFE, 0xFFFFFFFF, 0xDFFEFFFF, 0xFFFFFFFF, +0xFFFF007B, 0xFFFFFFFF, 0xFFFF199F, 0x033FCFFF, +0x00000000, 0xFFFE0000, 0x027FFFFF, 0xFFFFFFFE, +0xFFFE007F, 0xBBFFFFFB, 0xFFFF0016, 0x000707FF, +0x00000000, 0x07FFFFFE, 0x0007FFFF, 0xFFFF03FF, +0xFFFFFFFF, 0x7CFFFFFF, 0xFFEF7FFF, 0x03FF3DFF, +0xFFFFFFEE, 0xF3FFFFFF, 0xFF1E3FFF, 0x0000FFCF, +0xFFF99FEE, 0xD3C5FDFF, 0xB080399F, 0x0003FFCF, +0xFFF987E4, 0xD36DFDFF, 0x5E003987, 0x001FFFC0, +0xFFFBAFEE, 0xF3EDFDFF, 0x00003BBF, 0x0000FFC1, +0xFFF99FEE, 0xF3CDFDFF, 0xB0C0398F, 0x0000FFC3, +0xD63DC7EC, 0xC3BFC718, 0x00803DC7, 0x0000FF80, +0xFFFDDFEE, 0xC3EFFDFF, 0x00603DDF, 0x0000FFC3, +0xFFFDDFEC, 0xC3EFFDFF, 0x40603DDF, 0x0000FFC3, +0xFFFDDFEC, 0xC3FFFDFF, 0x00803DCF, 0x0000FFC3, +0x00000000, 0x00000000, 0x00000000, 0x00000000, +0xFFFFFFFE, 0x07FF7FFF, 0x03FF7FFF, 0x00000000, +0xFEF02596, 0x3BFF6CAE, 0x03FF3F5F, 0x00000000, +0x03000000, 0xC2A003FF, 0xFFFFFEFF, 0xFFFE03FF, +0xFEBF0FDF, 0x02FE3FFF, 0x00000000, 0x00000000, +0x00000000, 0x00000000, 0x00000000, 0x00000000, +0x00000000, 0x00000000, 0x1FFF0000, 0x00000002, +0x000000A0, 0x003EFFFE, 0xFFFFFFFE, 0xFFFFFFFF, +0x661FFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0x77FFFFFF, +}; +static const unsigned char nmstrtPages[] = { +0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, +0x00, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, +0x10, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x13, +0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x15, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x17, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x18, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +static const unsigned char namePages[] = { +0x19, 0x03, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x00, +0x00, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, +0x10, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x13, +0x26, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x27, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x17, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x18, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; diff --git a/expat/utf8tab.h b/expat/utf8tab.h new file mode 100644 index 00000000..7bb3e776 --- /dev/null +++ b/expat/utf8tab.h @@ -0,0 +1,37 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + + +/* 0x80 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0x84 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0x88 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0x8C */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0x90 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0x94 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0x98 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0x9C */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0xA0 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0xA4 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0xA8 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0xAC */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0xB0 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0xB4 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0xB8 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0xBC */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL, +/* 0xC0 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2, +/* 0xC4 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2, +/* 0xC8 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2, +/* 0xCC */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2, +/* 0xD0 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2, +/* 0xD4 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2, +/* 0xD8 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2, +/* 0xDC */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2, +/* 0xE0 */ BT_LEAD3, BT_LEAD3, BT_LEAD3, BT_LEAD3, +/* 0xE4 */ BT_LEAD3, BT_LEAD3, BT_LEAD3, BT_LEAD3, +/* 0xE8 */ BT_LEAD3, BT_LEAD3, BT_LEAD3, BT_LEAD3, +/* 0xEC */ BT_LEAD3, BT_LEAD3, BT_LEAD3, BT_LEAD3, +/* 0xF0 */ BT_LEAD4, BT_LEAD4, BT_LEAD4, BT_LEAD4, +/* 0xF4 */ BT_LEAD4, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0xF8 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML, +/* 0xFC */ BT_NONXML, BT_NONXML, BT_MALFORM, BT_MALFORM, diff --git a/expat/winconfig.h b/expat/winconfig.h new file mode 100644 index 00000000..922ba106 --- /dev/null +++ b/expat/winconfig.h @@ -0,0 +1,30 @@ +/*================================================================ +** Copyright 2000, Clark Cooper +** All rights reserved. +** +** This is free software. You are permitted to copy, distribute, or modify +** it under the terms of the MIT/X license (contained in the COPYING file +** with this distribution.) +*/ + +#ifndef WINCONFIG_H +#define WINCONFIG_H + +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN + +#include +#include + +#define XML_NS 1 +#define XML_DTD 1 +#define XML_CONTEXT_BYTES 1024 + +/* we will assume all Windows platforms are little endian */ +#define BYTEORDER 1234 + +/* Windows has memmove() available. */ +#define HAVE_MEMMOVE + +#endif /* ndef WINCONFIG_H */ diff --git a/expat/xmlparse.c b/expat/xmlparse.c new file mode 100644 index 00000000..fc4adbb0 --- /dev/null +++ b/expat/xmlparse.c @@ -0,0 +1,5814 @@ +/* Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +#include +#include /* memset(), memcpy() */ + +#define XML_BUILDING_EXPAT 1 + +#ifdef COMPILED_FROM_DSP +#include "winconfig.h" +#elif defined(MACOS_CLASSIC) +#include "macconfig.h" +#else +#ifdef HAVE_EXPAT_CONFIG_H +#include +#endif +#endif /* ndef COMPILED_FROM_DSP */ + +#include "expat.h" + +#ifdef XML_UNICODE +#define XML_ENCODE_MAX XML_UTF16_ENCODE_MAX +#define XmlConvert XmlUtf16Convert +#define XmlGetInternalEncoding XmlGetUtf16InternalEncoding +#define XmlGetInternalEncodingNS XmlGetUtf16InternalEncodingNS +#define XmlEncode XmlUtf16Encode +#define MUST_CONVERT(enc, s) (!(enc)->isUtf16 || (((unsigned long)s) & 1)) +typedef unsigned short ICHAR; +#else +#define XML_ENCODE_MAX XML_UTF8_ENCODE_MAX +#define XmlConvert XmlUtf8Convert +#define XmlGetInternalEncoding XmlGetUtf8InternalEncoding +#define XmlGetInternalEncodingNS XmlGetUtf8InternalEncodingNS +#define XmlEncode XmlUtf8Encode +#define MUST_CONVERT(enc, s) (!(enc)->isUtf8) +typedef char ICHAR; +#endif + + +#ifndef XML_NS + +#define XmlInitEncodingNS XmlInitEncoding +#define XmlInitUnknownEncodingNS XmlInitUnknownEncoding +#undef XmlGetInternalEncodingNS +#define XmlGetInternalEncodingNS XmlGetInternalEncoding +#define XmlParseXmlDeclNS XmlParseXmlDecl + +#endif + +#ifdef XML_UNICODE + +#ifdef XML_UNICODE_WCHAR_T +#define XML_T(x) (const wchar_t)x +#define XML_L(x) L ## x +#else +#define XML_T(x) (const unsigned short)x +#define XML_L(x) x +#endif + +#else + +#define XML_T(x) x +#define XML_L(x) x + +#endif + +/* Round up n to be a multiple of sz, where sz is a power of 2. */ +#define ROUND_UP(n, sz) (((n) + ((sz) - 1)) & ~((sz) - 1)) + +/* Handle the case where memmove() doesn't exist. */ +#ifndef HAVE_MEMMOVE +#ifdef HAVE_BCOPY +#define memmove(d,s,l) bcopy((s),(d),(l)) +#else +#error memmove does not exist on this platform, nor is a substitute available +#endif /* HAVE_BCOPY */ +#endif /* HAVE_MEMMOVE */ + +#include "internal.h" +#include "xmltok.h" +#include "xmlrole.h" + +typedef const XML_Char *KEY; + +typedef struct { + KEY name; +} NAMED; + +typedef struct { + NAMED **v; + unsigned char power; + size_t size; + size_t used; + const XML_Memory_Handling_Suite *mem; +} HASH_TABLE; + +/* Basic character hash algorithm, taken from Python's string hash: + h = h * 1000003 ^ character, the constant being a prime number. + +*/ +#ifdef XML_UNICODE +#define CHAR_HASH(h, c) \ + (((h) * 0xF4243) ^ (unsigned short)(c)) +#else +#define CHAR_HASH(h, c) \ + (((h) * 0xF4243) ^ (unsigned char)(c)) +#endif + +/* For probing (after a collision) we need a step size relative prime + to the hash table size, which is a power of 2. We use double-hashing, + since we can calculate a second hash value cheaply by taking those bits + of the first hash value that were discarded (masked out) when the table + index was calculated: index = hash & mask, where mask = table->size - 1. + We limit the maximum step size to table->size / 4 (mask >> 2) and make + it odd, since odd numbers are always relative prime to a power of 2. +*/ +#define SECOND_HASH(hash, mask, power) \ + ((((hash) & ~(mask)) >> ((power) - 1)) & ((mask) >> 2)) +#define PROBE_STEP(hash, mask, power) \ + ((unsigned char)((SECOND_HASH(hash, mask, power)) | 1)) + +typedef struct { + NAMED **p; + NAMED **end; +} HASH_TABLE_ITER; + +#define INIT_TAG_BUF_SIZE 32 /* must be a multiple of sizeof(XML_Char) */ +#define INIT_DATA_BUF_SIZE 1024 +#define INIT_ATTS_SIZE 16 +#define INIT_ATTS_VERSION 0xFFFFFFFF +#define INIT_BLOCK_SIZE 1024 +#define INIT_BUFFER_SIZE 1024 + +#define EXPAND_SPARE 24 + +typedef struct binding { + struct prefix *prefix; + struct binding *nextTagBinding; + struct binding *prevPrefixBinding; + const struct attribute_id *attId; + XML_Char *uri; + int uriLen; + int uriAlloc; +} BINDING; + +typedef struct prefix { + const XML_Char *name; + BINDING *binding; +} PREFIX; + +typedef struct { + const XML_Char *str; + const XML_Char *localPart; + const XML_Char *prefix; + int strLen; + int uriLen; + int prefixLen; +} TAG_NAME; + +/* TAG represents an open element. + The name of the element is stored in both the document and API + encodings. The memory buffer 'buf' is a separately-allocated + memory area which stores the name. During the XML_Parse()/ + XMLParseBuffer() when the element is open, the memory for the 'raw' + version of the name (in the document encoding) is shared with the + document buffer. If the element is open across calls to + XML_Parse()/XML_ParseBuffer(), the buffer is re-allocated to + contain the 'raw' name as well. + + A parser re-uses these structures, maintaining a list of allocated + TAG objects in a free list. +*/ +typedef struct tag { + struct tag *parent; /* parent of this element */ + const char *rawName; /* tagName in the original encoding */ + int rawNameLength; + TAG_NAME name; /* tagName in the API encoding */ + char *buf; /* buffer for name components */ + char *bufEnd; /* end of the buffer */ + BINDING *bindings; +} TAG; + +typedef struct { + const XML_Char *name; + const XML_Char *textPtr; + int textLen; + const XML_Char *systemId; + const XML_Char *base; + const XML_Char *publicId; + const XML_Char *notation; + XML_Bool open; + XML_Bool is_param; + XML_Bool is_internal; /* true if declared in internal subset outside PE */ +} ENTITY; + +typedef struct { + enum XML_Content_Type type; + enum XML_Content_Quant quant; + const XML_Char * name; + int firstchild; + int lastchild; + int childcnt; + int nextsib; +} CONTENT_SCAFFOLD; + +#define INIT_SCAFFOLD_ELEMENTS 32 + +typedef struct block { + struct block *next; + int size; + XML_Char s[1]; +} BLOCK; + +typedef struct { + BLOCK *blocks; + BLOCK *freeBlocks; + const XML_Char *end; + XML_Char *ptr; + XML_Char *start; + const XML_Memory_Handling_Suite *mem; +} STRING_POOL; + +/* The XML_Char before the name is used to determine whether + an attribute has been specified. */ +typedef struct attribute_id { + XML_Char *name; + PREFIX *prefix; + XML_Bool maybeTokenized; + XML_Bool xmlns; +} ATTRIBUTE_ID; + +typedef struct { + const ATTRIBUTE_ID *id; + XML_Bool isCdata; + const XML_Char *value; +} DEFAULT_ATTRIBUTE; + +typedef struct { + unsigned long version; + unsigned long hash; + const XML_Char *uriName; +} NS_ATT; + +typedef struct { + const XML_Char *name; + PREFIX *prefix; + const ATTRIBUTE_ID *idAtt; + int nDefaultAtts; + int allocDefaultAtts; + DEFAULT_ATTRIBUTE *defaultAtts; +} ELEMENT_TYPE; + +typedef struct { + HASH_TABLE generalEntities; + HASH_TABLE elementTypes; + HASH_TABLE attributeIds; + HASH_TABLE prefixes; + STRING_POOL pool; + STRING_POOL entityValuePool; + /* false once a parameter entity reference has been skipped */ + XML_Bool keepProcessing; + /* true once an internal or external PE reference has been encountered; + this includes the reference to an external subset */ + XML_Bool hasParamEntityRefs; + XML_Bool standalone; +#ifdef XML_DTD + /* indicates if external PE has been read */ + XML_Bool paramEntityRead; + HASH_TABLE paramEntities; +#endif /* XML_DTD */ + PREFIX defaultPrefix; + /* === scaffolding for building content model === */ + XML_Bool in_eldecl; + CONTENT_SCAFFOLD *scaffold; + unsigned contentStringLen; + unsigned scaffSize; + unsigned scaffCount; + int scaffLevel; + int *scaffIndex; +} DTD; + +typedef struct open_internal_entity { + const char *internalEventPtr; + const char *internalEventEndPtr; + struct open_internal_entity *next; + ENTITY *entity; +} OPEN_INTERNAL_ENTITY; + +typedef enum XML_Error PTRCALL Processor(XML_Parser parser, + const char *start, + const char *end, + const char **endPtr); + +static Processor prologProcessor; +static Processor prologInitProcessor; +static Processor contentProcessor; +static Processor cdataSectionProcessor; +#ifdef XML_DTD +static Processor ignoreSectionProcessor; +static Processor externalParEntProcessor; +static Processor externalParEntInitProcessor; +static Processor entityValueProcessor; +static Processor entityValueInitProcessor; +#endif /* XML_DTD */ +static Processor epilogProcessor; +static Processor errorProcessor; +static Processor externalEntityInitProcessor; +static Processor externalEntityInitProcessor2; +static Processor externalEntityInitProcessor3; +static Processor externalEntityContentProcessor; + +static enum XML_Error +handleUnknownEncoding(XML_Parser parser, const XML_Char *encodingName); +static enum XML_Error +processXmlDecl(XML_Parser parser, int isGeneralTextEntity, + const char *, const char *); +static enum XML_Error +initializeEncoding(XML_Parser parser); +static enum XML_Error +doProlog(XML_Parser parser, const ENCODING *enc, const char *s, + const char *end, int tok, const char *next, const char **nextPtr); +static enum XML_Error +processInternalParamEntity(XML_Parser parser, ENTITY *entity); +static enum XML_Error +doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, + const char *start, const char *end, const char **endPtr); +static enum XML_Error +doCdataSection(XML_Parser parser, const ENCODING *, const char **startPtr, + const char *end, const char **nextPtr); +#ifdef XML_DTD +static enum XML_Error +doIgnoreSection(XML_Parser parser, const ENCODING *, const char **startPtr, + const char *end, const char **nextPtr); +#endif /* XML_DTD */ + +static enum XML_Error +storeAtts(XML_Parser parser, const ENCODING *, const char *s, + TAG_NAME *tagNamePtr, BINDING **bindingsPtr); +static enum XML_Error +addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, + const XML_Char *uri, BINDING **bindingsPtr); +static int +defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *, + XML_Bool isCdata, XML_Bool isId, const XML_Char *dfltValue, + XML_Parser parser); +static enum XML_Error +storeAttributeValue(XML_Parser parser, const ENCODING *, XML_Bool isCdata, + const char *, const char *, STRING_POOL *); +static enum XML_Error +appendAttributeValue(XML_Parser parser, const ENCODING *, XML_Bool isCdata, + const char *, const char *, STRING_POOL *); +static ATTRIBUTE_ID * +getAttributeId(XML_Parser parser, const ENCODING *enc, const char *start, + const char *end); +static int +setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *); +static enum XML_Error +storeEntityValue(XML_Parser parser, const ENCODING *enc, const char *start, + const char *end); +static int +reportProcessingInstruction(XML_Parser parser, const ENCODING *enc, + const char *start, const char *end); +static int +reportComment(XML_Parser parser, const ENCODING *enc, const char *start, + const char *end); +static void +reportDefault(XML_Parser parser, const ENCODING *enc, const char *start, + const char *end); + +static const XML_Char * getContext(XML_Parser parser); +static XML_Bool +setContext(XML_Parser parser, const XML_Char *context); + +static void FASTCALL normalizePublicId(XML_Char *s); + +static DTD * dtdCreate(const XML_Memory_Handling_Suite *ms); +/* do not call if parentParser != NULL */ +static void dtdReset(DTD *p, const XML_Memory_Handling_Suite *ms); +static void +dtdDestroy(DTD *p, XML_Bool isDocEntity, const XML_Memory_Handling_Suite *ms); +static int +dtdCopy(DTD *newDtd, const DTD *oldDtd, const XML_Memory_Handling_Suite *ms); +static int +copyEntityTable(HASH_TABLE *, STRING_POOL *, const HASH_TABLE *); + +static NAMED * +lookup(HASH_TABLE *table, KEY name, size_t createSize); +static void FASTCALL +hashTableInit(HASH_TABLE *, const XML_Memory_Handling_Suite *ms); +static void FASTCALL hashTableClear(HASH_TABLE *); +static void FASTCALL hashTableDestroy(HASH_TABLE *); +static void FASTCALL +hashTableIterInit(HASH_TABLE_ITER *, const HASH_TABLE *); +static NAMED * FASTCALL hashTableIterNext(HASH_TABLE_ITER *); + +static void FASTCALL +poolInit(STRING_POOL *, const XML_Memory_Handling_Suite *ms); +static void FASTCALL poolClear(STRING_POOL *); +static void FASTCALL poolDestroy(STRING_POOL *); +static XML_Char * +poolAppend(STRING_POOL *pool, const ENCODING *enc, + const char *ptr, const char *end); +static XML_Char * +poolStoreString(STRING_POOL *pool, const ENCODING *enc, + const char *ptr, const char *end); +static XML_Bool FASTCALL poolGrow(STRING_POOL *pool); +static const XML_Char * FASTCALL +poolCopyString(STRING_POOL *pool, const XML_Char *s); +static const XML_Char * +poolCopyStringN(STRING_POOL *pool, const XML_Char *s, int n); +static const XML_Char * FASTCALL +poolAppendString(STRING_POOL *pool, const XML_Char *s); + +static int FASTCALL nextScaffoldPart(XML_Parser parser); +static XML_Content * build_model(XML_Parser parser); +static ELEMENT_TYPE * +getElementType(XML_Parser parser, const ENCODING *enc, + const char *ptr, const char *end); + +static XML_Parser +parserCreate(const XML_Char *encodingName, + const XML_Memory_Handling_Suite *memsuite, + const XML_Char *nameSep, + DTD *dtd); +static void +parserInit(XML_Parser parser, const XML_Char *encodingName); + +#define poolStart(pool) ((pool)->start) +#define poolEnd(pool) ((pool)->ptr) +#define poolLength(pool) ((pool)->ptr - (pool)->start) +#define poolChop(pool) ((void)--(pool->ptr)) +#define poolLastChar(pool) (((pool)->ptr)[-1]) +#define poolDiscard(pool) ((pool)->ptr = (pool)->start) +#define poolFinish(pool) ((pool)->start = (pool)->ptr) +#define poolAppendChar(pool, c) \ + (((pool)->ptr == (pool)->end && !poolGrow(pool)) \ + ? 0 \ + : ((*((pool)->ptr)++ = c), 1)) + +struct XML_ParserStruct { + /* The first member must be userData so that the XML_GetUserData + macro works. */ + void *m_userData; + void *m_handlerArg; + char *m_buffer; + const XML_Memory_Handling_Suite m_mem; + /* first character to be parsed */ + const char *m_bufferPtr; + /* past last character to be parsed */ + char *m_bufferEnd; + /* allocated end of buffer */ + const char *m_bufferLim; + long m_parseEndByteIndex; + const char *m_parseEndPtr; + XML_Char *m_dataBuf; + XML_Char *m_dataBufEnd; + XML_StartElementHandler m_startElementHandler; + XML_EndElementHandler m_endElementHandler; + XML_CharacterDataHandler m_characterDataHandler; + XML_ProcessingInstructionHandler m_processingInstructionHandler; + XML_CommentHandler m_commentHandler; + XML_StartCdataSectionHandler m_startCdataSectionHandler; + XML_EndCdataSectionHandler m_endCdataSectionHandler; + XML_DefaultHandler m_defaultHandler; + XML_StartDoctypeDeclHandler m_startDoctypeDeclHandler; + XML_EndDoctypeDeclHandler m_endDoctypeDeclHandler; + XML_UnparsedEntityDeclHandler m_unparsedEntityDeclHandler; + XML_NotationDeclHandler m_notationDeclHandler; + XML_StartNamespaceDeclHandler m_startNamespaceDeclHandler; + XML_EndNamespaceDeclHandler m_endNamespaceDeclHandler; + XML_NotStandaloneHandler m_notStandaloneHandler; + XML_ExternalEntityRefHandler m_externalEntityRefHandler; + XML_Parser m_externalEntityRefHandlerArg; + XML_SkippedEntityHandler m_skippedEntityHandler; + XML_UnknownEncodingHandler m_unknownEncodingHandler; + XML_ElementDeclHandler m_elementDeclHandler; + XML_AttlistDeclHandler m_attlistDeclHandler; + XML_EntityDeclHandler m_entityDeclHandler; + XML_XmlDeclHandler m_xmlDeclHandler; + const ENCODING *m_encoding; + INIT_ENCODING m_initEncoding; + const ENCODING *m_internalEncoding; + const XML_Char *m_protocolEncodingName; + XML_Bool m_ns; + XML_Bool m_ns_triplets; + void *m_unknownEncodingMem; + void *m_unknownEncodingData; + void *m_unknownEncodingHandlerData; + void (*m_unknownEncodingRelease)(void *); + PROLOG_STATE m_prologState; + Processor *m_processor; + enum XML_Error m_errorCode; + const char *m_eventPtr; + const char *m_eventEndPtr; + const char *m_positionPtr; + OPEN_INTERNAL_ENTITY *m_openInternalEntities; + XML_Bool m_defaultExpandInternalEntities; + int m_tagLevel; + ENTITY *m_declEntity; + const XML_Char *m_doctypeName; + const XML_Char *m_doctypeSysid; + const XML_Char *m_doctypePubid; + const XML_Char *m_declAttributeType; + const XML_Char *m_declNotationName; + const XML_Char *m_declNotationPublicId; + ELEMENT_TYPE *m_declElementType; + ATTRIBUTE_ID *m_declAttributeId; + XML_Bool m_declAttributeIsCdata; + XML_Bool m_declAttributeIsId; + DTD *m_dtd; + const XML_Char *m_curBase; + TAG *m_tagStack; + TAG *m_freeTagList; + BINDING *m_inheritedBindings; + BINDING *m_freeBindingList; + int m_attsSize; + int m_nSpecifiedAtts; + int m_idAttIndex; + ATTRIBUTE *m_atts; + NS_ATT *m_nsAtts; + unsigned long m_nsAttsVersion; + unsigned char m_nsAttsPower; + POSITION m_position; + STRING_POOL m_tempPool; + STRING_POOL m_temp2Pool; + char *m_groupConnector; + unsigned int m_groupSize; + XML_Char m_namespaceSeparator; + XML_Parser m_parentParser; +#ifdef XML_DTD + XML_Bool m_isParamEntity; + XML_Bool m_useForeignDTD; + enum XML_ParamEntityParsing m_paramEntityParsing; +#endif +}; + +#define MALLOC(s) (parser->m_mem.malloc_fcn((s))) +#define REALLOC(p,s) (parser->m_mem.realloc_fcn((p),(s))) +#define FREE(p) (parser->m_mem.free_fcn((p))) + +#define userData (parser->m_userData) +#define handlerArg (parser->m_handlerArg) +#define startElementHandler (parser->m_startElementHandler) +#define endElementHandler (parser->m_endElementHandler) +#define characterDataHandler (parser->m_characterDataHandler) +#define processingInstructionHandler \ + (parser->m_processingInstructionHandler) +#define commentHandler (parser->m_commentHandler) +#define startCdataSectionHandler \ + (parser->m_startCdataSectionHandler) +#define endCdataSectionHandler (parser->m_endCdataSectionHandler) +#define defaultHandler (parser->m_defaultHandler) +#define startDoctypeDeclHandler (parser->m_startDoctypeDeclHandler) +#define endDoctypeDeclHandler (parser->m_endDoctypeDeclHandler) +#define unparsedEntityDeclHandler \ + (parser->m_unparsedEntityDeclHandler) +#define notationDeclHandler (parser->m_notationDeclHandler) +#define startNamespaceDeclHandler \ + (parser->m_startNamespaceDeclHandler) +#define endNamespaceDeclHandler (parser->m_endNamespaceDeclHandler) +#define notStandaloneHandler (parser->m_notStandaloneHandler) +#define externalEntityRefHandler \ + (parser->m_externalEntityRefHandler) +#define externalEntityRefHandlerArg \ + (parser->m_externalEntityRefHandlerArg) +#define internalEntityRefHandler \ + (parser->m_internalEntityRefHandler) +#define skippedEntityHandler (parser->m_skippedEntityHandler) +#define unknownEncodingHandler (parser->m_unknownEncodingHandler) +#define elementDeclHandler (parser->m_elementDeclHandler) +#define attlistDeclHandler (parser->m_attlistDeclHandler) +#define entityDeclHandler (parser->m_entityDeclHandler) +#define xmlDeclHandler (parser->m_xmlDeclHandler) +#define encoding (parser->m_encoding) +#define initEncoding (parser->m_initEncoding) +#define internalEncoding (parser->m_internalEncoding) +#define unknownEncodingMem (parser->m_unknownEncodingMem) +#define unknownEncodingData (parser->m_unknownEncodingData) +#define unknownEncodingHandlerData \ + (parser->m_unknownEncodingHandlerData) +#define unknownEncodingRelease (parser->m_unknownEncodingRelease) +#define protocolEncodingName (parser->m_protocolEncodingName) +#define ns (parser->m_ns) +#define ns_triplets (parser->m_ns_triplets) +#define prologState (parser->m_prologState) +#define processor (parser->m_processor) +#define errorCode (parser->m_errorCode) +#define eventPtr (parser->m_eventPtr) +#define eventEndPtr (parser->m_eventEndPtr) +#define positionPtr (parser->m_positionPtr) +#define position (parser->m_position) +#define openInternalEntities (parser->m_openInternalEntities) +#define defaultExpandInternalEntities \ + (parser->m_defaultExpandInternalEntities) +#define tagLevel (parser->m_tagLevel) +#define buffer (parser->m_buffer) +#define bufferPtr (parser->m_bufferPtr) +#define bufferEnd (parser->m_bufferEnd) +#define parseEndByteIndex (parser->m_parseEndByteIndex) +#define parseEndPtr (parser->m_parseEndPtr) +#define bufferLim (parser->m_bufferLim) +#define dataBuf (parser->m_dataBuf) +#define dataBufEnd (parser->m_dataBufEnd) +#define _dtd (parser->m_dtd) +#define curBase (parser->m_curBase) +#define declEntity (parser->m_declEntity) +#define doctypeName (parser->m_doctypeName) +#define doctypeSysid (parser->m_doctypeSysid) +#define doctypePubid (parser->m_doctypePubid) +#define declAttributeType (parser->m_declAttributeType) +#define declNotationName (parser->m_declNotationName) +#define declNotationPublicId (parser->m_declNotationPublicId) +#define declElementType (parser->m_declElementType) +#define declAttributeId (parser->m_declAttributeId) +#define declAttributeIsCdata (parser->m_declAttributeIsCdata) +#define declAttributeIsId (parser->m_declAttributeIsId) +#define freeTagList (parser->m_freeTagList) +#define freeBindingList (parser->m_freeBindingList) +#define inheritedBindings (parser->m_inheritedBindings) +#define tagStack (parser->m_tagStack) +#define atts (parser->m_atts) +#define attsSize (parser->m_attsSize) +#define nSpecifiedAtts (parser->m_nSpecifiedAtts) +#define idAttIndex (parser->m_idAttIndex) +#define nsAtts (parser->m_nsAtts) +#define nsAttsVersion (parser->m_nsAttsVersion) +#define nsAttsPower (parser->m_nsAttsPower) +#define tempPool (parser->m_tempPool) +#define temp2Pool (parser->m_temp2Pool) +#define groupConnector (parser->m_groupConnector) +#define groupSize (parser->m_groupSize) +#define namespaceSeparator (parser->m_namespaceSeparator) +#define parentParser (parser->m_parentParser) +#ifdef XML_DTD +#define isParamEntity (parser->m_isParamEntity) +#define useForeignDTD (parser->m_useForeignDTD) +#define paramEntityParsing (parser->m_paramEntityParsing) +#endif /* XML_DTD */ + +#ifdef XML_DTD +#define parsing \ + (parentParser \ + ? \ + (isParamEntity \ + ? \ + (processor != externalParEntInitProcessor) \ + : \ + (processor != externalEntityInitProcessor)) \ + : \ + (processor != prologInitProcessor)) +#else +#define parsing \ + (parentParser \ + ? \ + (processor != externalEntityInitProcessor) \ + : \ + (processor != prologInitProcessor)) +#endif /* XML_DTD */ + +XML_Parser XMLCALL +XML_ParserCreate(const XML_Char *encodingName) +{ + return XML_ParserCreate_MM(encodingName, NULL, NULL); +} + +XML_Parser XMLCALL +XML_ParserCreateNS(const XML_Char *encodingName, XML_Char nsSep) +{ + XML_Char tmp[2]; + *tmp = nsSep; + return XML_ParserCreate_MM(encodingName, NULL, tmp); +} + +static const XML_Char implicitContext[] = { + 'x', 'm', 'l', '=', 'h', 't', 't', 'p', ':', '/', '/', + 'w', 'w', 'w', '.', 'w', '3', '.', 'o', 'r', 'g', '/', + 'X', 'M', 'L', '/', '1', '9', '9', '8', '/', + 'n', 'a', 'm', 'e', 's', 'p', 'a', 'c', 'e', '\0' +}; + +XML_Parser XMLCALL +XML_ParserCreate_MM(const XML_Char *encodingName, + const XML_Memory_Handling_Suite *memsuite, + const XML_Char *nameSep) +{ + XML_Parser parser = parserCreate(encodingName, memsuite, nameSep, NULL); + if (parser != NULL && ns) { + /* implicit context only set for root parser, since child + parsers (i.e. external entity parsers) will inherit it + */ + if (!setContext(parser, implicitContext)) { + XML_ParserFree(parser); + return NULL; + } + } + return parser; +} + +static XML_Parser +parserCreate(const XML_Char *encodingName, + const XML_Memory_Handling_Suite *memsuite, + const XML_Char *nameSep, + DTD *dtd) +{ + XML_Parser parser; + + if (memsuite) { + XML_Memory_Handling_Suite *mtemp; + parser = (XML_Parser) + memsuite->malloc_fcn(sizeof(struct XML_ParserStruct)); + if (parser != NULL) { + mtemp = (XML_Memory_Handling_Suite *)&(parser->m_mem); + mtemp->malloc_fcn = memsuite->malloc_fcn; + mtemp->realloc_fcn = memsuite->realloc_fcn; + mtemp->free_fcn = memsuite->free_fcn; + } + } + else { + XML_Memory_Handling_Suite *mtemp; + parser = (XML_Parser)malloc(sizeof(struct XML_ParserStruct)); + if (parser != NULL) { + mtemp = (XML_Memory_Handling_Suite *)&(parser->m_mem); + mtemp->malloc_fcn = malloc; + mtemp->realloc_fcn = realloc; + mtemp->free_fcn = free; + } + } + + if (!parser) + return parser; + + buffer = NULL; + bufferLim = NULL; + + attsSize = INIT_ATTS_SIZE; + atts = (ATTRIBUTE *)MALLOC(attsSize * sizeof(ATTRIBUTE)); + if (atts == NULL) { + FREE(parser); + return NULL; + } + dataBuf = (XML_Char *)MALLOC(INIT_DATA_BUF_SIZE * sizeof(XML_Char)); + if (dataBuf == NULL) { + FREE(atts); + FREE(parser); + return NULL; + } + dataBufEnd = dataBuf + INIT_DATA_BUF_SIZE; + + if (dtd) + _dtd = dtd; + else { + _dtd = dtdCreate(&parser->m_mem); + if (_dtd == NULL) { + FREE(dataBuf); + FREE(atts); + FREE(parser); + return NULL; + } + } + + freeBindingList = NULL; + freeTagList = NULL; + + groupSize = 0; + groupConnector = NULL; + + unknownEncodingHandler = NULL; + unknownEncodingHandlerData = NULL; + + namespaceSeparator = '!'; + ns = XML_FALSE; + ns_triplets = XML_FALSE; + + nsAtts = NULL; + nsAttsVersion = 0; + nsAttsPower = 0; + + poolInit(&tempPool, &(parser->m_mem)); + poolInit(&temp2Pool, &(parser->m_mem)); + parserInit(parser, encodingName); + + if (encodingName && !protocolEncodingName) { + XML_ParserFree(parser); + return NULL; + } + + if (nameSep) { + ns = XML_TRUE; + internalEncoding = XmlGetInternalEncodingNS(); + namespaceSeparator = *nameSep; + } + else { + internalEncoding = XmlGetInternalEncoding(); + } + + return parser; +} + +static void +parserInit(XML_Parser parser, const XML_Char *encodingName) +{ + processor = prologInitProcessor; + XmlPrologStateInit(&prologState); + protocolEncodingName = (encodingName != NULL + ? poolCopyString(&tempPool, encodingName) + : NULL); + curBase = NULL; + XmlInitEncoding(&initEncoding, &encoding, 0); + userData = NULL; + handlerArg = NULL; + startElementHandler = NULL; + endElementHandler = NULL; + characterDataHandler = NULL; + processingInstructionHandler = NULL; + commentHandler = NULL; + startCdataSectionHandler = NULL; + endCdataSectionHandler = NULL; + defaultHandler = NULL; + startDoctypeDeclHandler = NULL; + endDoctypeDeclHandler = NULL; + unparsedEntityDeclHandler = NULL; + notationDeclHandler = NULL; + startNamespaceDeclHandler = NULL; + endNamespaceDeclHandler = NULL; + notStandaloneHandler = NULL; + externalEntityRefHandler = NULL; + externalEntityRefHandlerArg = parser; + skippedEntityHandler = NULL; + elementDeclHandler = NULL; + attlistDeclHandler = NULL; + entityDeclHandler = NULL; + xmlDeclHandler = NULL; + bufferPtr = buffer; + bufferEnd = buffer; + parseEndByteIndex = 0; + parseEndPtr = NULL; + declElementType = NULL; + declAttributeId = NULL; + declEntity = NULL; + doctypeName = NULL; + doctypeSysid = NULL; + doctypePubid = NULL; + declAttributeType = NULL; + declNotationName = NULL; + declNotationPublicId = NULL; + declAttributeIsCdata = XML_FALSE; + declAttributeIsId = XML_FALSE; + memset(&position, 0, sizeof(POSITION)); + errorCode = XML_ERROR_NONE; + eventPtr = NULL; + eventEndPtr = NULL; + positionPtr = NULL; + openInternalEntities = 0; + defaultExpandInternalEntities = XML_TRUE; + tagLevel = 0; + tagStack = NULL; + inheritedBindings = NULL; + nSpecifiedAtts = 0; + unknownEncodingMem = NULL; + unknownEncodingRelease = NULL; + unknownEncodingData = NULL; + parentParser = NULL; +#ifdef XML_DTD + isParamEntity = XML_FALSE; + useForeignDTD = XML_FALSE; + paramEntityParsing = XML_PARAM_ENTITY_PARSING_NEVER; +#endif +} + +/* moves list of bindings to freeBindingList */ +static void FASTCALL +moveToFreeBindingList(XML_Parser parser, BINDING *bindings) +{ + while (bindings) { + BINDING *b = bindings; + bindings = bindings->nextTagBinding; + b->nextTagBinding = freeBindingList; + freeBindingList = b; + } +} + +XML_Bool XMLCALL +XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) +{ + TAG *tStk; + if (parentParser) + return XML_FALSE; + /* move tagStack to freeTagList */ + tStk = tagStack; + while (tStk) { + TAG *tag = tStk; + tStk = tStk->parent; + tag->parent = freeTagList; + moveToFreeBindingList(parser, tag->bindings); + tag->bindings = NULL; + freeTagList = tag; + } + moveToFreeBindingList(parser, inheritedBindings); + FREE(unknownEncodingMem); + if (unknownEncodingRelease) + unknownEncodingRelease(unknownEncodingData); + poolClear(&tempPool); + poolClear(&temp2Pool); + parserInit(parser, encodingName); + dtdReset(_dtd, &parser->m_mem); + return setContext(parser, implicitContext); +} + +enum XML_Status XMLCALL +XML_SetEncoding(XML_Parser parser, const XML_Char *encodingName) +{ + /* Block after XML_Parse()/XML_ParseBuffer() has been called. + XXX There's no way for the caller to determine which of the + XXX possible error cases caused the XML_STATUS_ERROR return. + */ + if (parsing) + return XML_STATUS_ERROR; + if (encodingName == NULL) + protocolEncodingName = NULL; + else { + protocolEncodingName = poolCopyString(&tempPool, encodingName); + if (!protocolEncodingName) + return XML_STATUS_ERROR; + } + return XML_STATUS_OK; +} + +XML_Parser XMLCALL +XML_ExternalEntityParserCreate(XML_Parser oldParser, + const XML_Char *context, + const XML_Char *encodingName) +{ + XML_Parser parser = oldParser; + DTD *newDtd = NULL; + DTD *oldDtd = _dtd; + XML_StartElementHandler oldStartElementHandler = startElementHandler; + XML_EndElementHandler oldEndElementHandler = endElementHandler; + XML_CharacterDataHandler oldCharacterDataHandler = characterDataHandler; + XML_ProcessingInstructionHandler oldProcessingInstructionHandler + = processingInstructionHandler; + XML_CommentHandler oldCommentHandler = commentHandler; + XML_StartCdataSectionHandler oldStartCdataSectionHandler + = startCdataSectionHandler; + XML_EndCdataSectionHandler oldEndCdataSectionHandler + = endCdataSectionHandler; + XML_DefaultHandler oldDefaultHandler = defaultHandler; + XML_UnparsedEntityDeclHandler oldUnparsedEntityDeclHandler + = unparsedEntityDeclHandler; + XML_NotationDeclHandler oldNotationDeclHandler = notationDeclHandler; + XML_StartNamespaceDeclHandler oldStartNamespaceDeclHandler + = startNamespaceDeclHandler; + XML_EndNamespaceDeclHandler oldEndNamespaceDeclHandler + = endNamespaceDeclHandler; + XML_NotStandaloneHandler oldNotStandaloneHandler = notStandaloneHandler; + XML_ExternalEntityRefHandler oldExternalEntityRefHandler + = externalEntityRefHandler; + XML_SkippedEntityHandler oldSkippedEntityHandler = skippedEntityHandler; + XML_UnknownEncodingHandler oldUnknownEncodingHandler + = unknownEncodingHandler; + XML_ElementDeclHandler oldElementDeclHandler = elementDeclHandler; + XML_AttlistDeclHandler oldAttlistDeclHandler = attlistDeclHandler; + XML_EntityDeclHandler oldEntityDeclHandler = entityDeclHandler; + XML_XmlDeclHandler oldXmlDeclHandler = xmlDeclHandler; + ELEMENT_TYPE * oldDeclElementType = declElementType; + + void *oldUserData = userData; + void *oldHandlerArg = handlerArg; + XML_Bool oldDefaultExpandInternalEntities = defaultExpandInternalEntities; + XML_Parser oldExternalEntityRefHandlerArg = externalEntityRefHandlerArg; +#ifdef XML_DTD + enum XML_ParamEntityParsing oldParamEntityParsing = paramEntityParsing; + int oldInEntityValue = prologState.inEntityValue; +#endif + XML_Bool oldns_triplets = ns_triplets; + +#ifdef XML_DTD + if (!context) + newDtd = oldDtd; +#endif /* XML_DTD */ + + /* Note that the magical uses of the pre-processor to make field + access look more like C++ require that `parser' be overwritten + here. This makes this function more painful to follow than it + would be otherwise. + */ + if (ns) { + XML_Char tmp[2]; + *tmp = namespaceSeparator; + parser = parserCreate(encodingName, &parser->m_mem, tmp, newDtd); + } + else { + parser = parserCreate(encodingName, &parser->m_mem, NULL, newDtd); + } + + if (!parser) + return NULL; + + startElementHandler = oldStartElementHandler; + endElementHandler = oldEndElementHandler; + characterDataHandler = oldCharacterDataHandler; + processingInstructionHandler = oldProcessingInstructionHandler; + commentHandler = oldCommentHandler; + startCdataSectionHandler = oldStartCdataSectionHandler; + endCdataSectionHandler = oldEndCdataSectionHandler; + defaultHandler = oldDefaultHandler; + unparsedEntityDeclHandler = oldUnparsedEntityDeclHandler; + notationDeclHandler = oldNotationDeclHandler; + startNamespaceDeclHandler = oldStartNamespaceDeclHandler; + endNamespaceDeclHandler = oldEndNamespaceDeclHandler; + notStandaloneHandler = oldNotStandaloneHandler; + externalEntityRefHandler = oldExternalEntityRefHandler; + skippedEntityHandler = oldSkippedEntityHandler; + unknownEncodingHandler = oldUnknownEncodingHandler; + elementDeclHandler = oldElementDeclHandler; + attlistDeclHandler = oldAttlistDeclHandler; + entityDeclHandler = oldEntityDeclHandler; + xmlDeclHandler = oldXmlDeclHandler; + declElementType = oldDeclElementType; + userData = oldUserData; + if (oldUserData == oldHandlerArg) + handlerArg = userData; + else + handlerArg = parser; + if (oldExternalEntityRefHandlerArg != oldParser) + externalEntityRefHandlerArg = oldExternalEntityRefHandlerArg; + defaultExpandInternalEntities = oldDefaultExpandInternalEntities; + ns_triplets = oldns_triplets; + parentParser = oldParser; +#ifdef XML_DTD + paramEntityParsing = oldParamEntityParsing; + prologState.inEntityValue = oldInEntityValue; + if (context) { +#endif /* XML_DTD */ + if (!dtdCopy(_dtd, oldDtd, &parser->m_mem) + || !setContext(parser, context)) { + XML_ParserFree(parser); + return NULL; + } + processor = externalEntityInitProcessor; +#ifdef XML_DTD + } + else { + /* The DTD instance referenced by _dtd is shared between the document's + root parser and external PE parsers, therefore one does not need to + call setContext. In addition, one also *must* not call setContext, + because this would overwrite existing prefix->binding pointers in + _dtd with ones that get destroyed with the external PE parser. + This would leave those prefixes with dangling pointers. + */ + isParamEntity = XML_TRUE; + XmlPrologStateInitExternalEntity(&prologState); + processor = externalParEntInitProcessor; + } +#endif /* XML_DTD */ + return parser; +} + +static void FASTCALL +destroyBindings(BINDING *bindings, XML_Parser parser) +{ + for (;;) { + BINDING *b = bindings; + if (!b) + break; + bindings = b->nextTagBinding; + FREE(b->uri); + FREE(b); + } +} + +void XMLCALL +XML_ParserFree(XML_Parser parser) +{ + for (;;) { + TAG *p; + if (tagStack == NULL) { + if (freeTagList == NULL) + break; + tagStack = freeTagList; + freeTagList = NULL; + } + p = tagStack; + tagStack = tagStack->parent; + FREE(p->buf); + destroyBindings(p->bindings, parser); + FREE(p); + } + destroyBindings(freeBindingList, parser); + destroyBindings(inheritedBindings, parser); + poolDestroy(&tempPool); + poolDestroy(&temp2Pool); +#ifdef XML_DTD + /* external parameter entity parsers share the DTD structure + parser->m_dtd with the root parser, so we must not destroy it + */ + if (!isParamEntity && _dtd) +#else + if (_dtd) +#endif /* XML_DTD */ + dtdDestroy(_dtd, (XML_Bool)!parentParser, &parser->m_mem); + FREE((void *)atts); + FREE(groupConnector); + FREE(buffer); + FREE(dataBuf); + FREE(nsAtts); + FREE(unknownEncodingMem); + if (unknownEncodingRelease) + unknownEncodingRelease(unknownEncodingData); + FREE(parser); +} + +void XMLCALL +XML_UseParserAsHandlerArg(XML_Parser parser) +{ + handlerArg = parser; +} + +enum XML_Error XMLCALL +XML_UseForeignDTD(XML_Parser parser, XML_Bool useDTD) +{ +#ifdef XML_DTD + /* block after XML_Parse()/XML_ParseBuffer() has been called */ + if (parsing) + return XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING; + useForeignDTD = useDTD; + return XML_ERROR_NONE; +#else + return XML_ERROR_FEATURE_REQUIRES_XML_DTD; +#endif +} + +void XMLCALL +XML_SetReturnNSTriplet(XML_Parser parser, int do_nst) +{ + /* block after XML_Parse()/XML_ParseBuffer() has been called */ + if (parsing) + return; + ns_triplets = do_nst ? XML_TRUE : XML_FALSE; +} + +void XMLCALL +XML_SetUserData(XML_Parser parser, void *p) +{ + if (handlerArg == userData) + handlerArg = userData = p; + else + userData = p; +} + +enum XML_Status XMLCALL +XML_SetBase(XML_Parser parser, const XML_Char *p) +{ + if (p) { + p = poolCopyString(&_dtd->pool, p); + if (!p) + return XML_STATUS_ERROR; + curBase = p; + } + else + curBase = NULL; + return XML_STATUS_OK; +} + +const XML_Char * XMLCALL +XML_GetBase(XML_Parser parser) +{ + return curBase; +} + +int XMLCALL +XML_GetSpecifiedAttributeCount(XML_Parser parser) +{ + return nSpecifiedAtts; +} + +int XMLCALL +XML_GetIdAttributeIndex(XML_Parser parser) +{ + return idAttIndex; +} + +void XMLCALL +XML_SetElementHandler(XML_Parser parser, + XML_StartElementHandler start, + XML_EndElementHandler end) +{ + startElementHandler = start; + endElementHandler = end; +} + +void XMLCALL +XML_SetStartElementHandler(XML_Parser parser, + XML_StartElementHandler start) { + startElementHandler = start; +} + +void XMLCALL +XML_SetEndElementHandler(XML_Parser parser, + XML_EndElementHandler end) { + endElementHandler = end; +} + +void XMLCALL +XML_SetCharacterDataHandler(XML_Parser parser, + XML_CharacterDataHandler handler) +{ + characterDataHandler = handler; +} + +void XMLCALL +XML_SetProcessingInstructionHandler(XML_Parser parser, + XML_ProcessingInstructionHandler handler) +{ + processingInstructionHandler = handler; +} + +void XMLCALL +XML_SetCommentHandler(XML_Parser parser, + XML_CommentHandler handler) +{ + commentHandler = handler; +} + +void XMLCALL +XML_SetCdataSectionHandler(XML_Parser parser, + XML_StartCdataSectionHandler start, + XML_EndCdataSectionHandler end) +{ + startCdataSectionHandler = start; + endCdataSectionHandler = end; +} + +void XMLCALL +XML_SetStartCdataSectionHandler(XML_Parser parser, + XML_StartCdataSectionHandler start) { + startCdataSectionHandler = start; +} + +void XMLCALL +XML_SetEndCdataSectionHandler(XML_Parser parser, + XML_EndCdataSectionHandler end) { + endCdataSectionHandler = end; +} + +void XMLCALL +XML_SetDefaultHandler(XML_Parser parser, + XML_DefaultHandler handler) +{ + defaultHandler = handler; + defaultExpandInternalEntities = XML_FALSE; +} + +void XMLCALL +XML_SetDefaultHandlerExpand(XML_Parser parser, + XML_DefaultHandler handler) +{ + defaultHandler = handler; + defaultExpandInternalEntities = XML_TRUE; +} + +void XMLCALL +XML_SetDoctypeDeclHandler(XML_Parser parser, + XML_StartDoctypeDeclHandler start, + XML_EndDoctypeDeclHandler end) +{ + startDoctypeDeclHandler = start; + endDoctypeDeclHandler = end; +} + +void XMLCALL +XML_SetStartDoctypeDeclHandler(XML_Parser parser, + XML_StartDoctypeDeclHandler start) { + startDoctypeDeclHandler = start; +} + +void XMLCALL +XML_SetEndDoctypeDeclHandler(XML_Parser parser, + XML_EndDoctypeDeclHandler end) { + endDoctypeDeclHandler = end; +} + +void XMLCALL +XML_SetUnparsedEntityDeclHandler(XML_Parser parser, + XML_UnparsedEntityDeclHandler handler) +{ + unparsedEntityDeclHandler = handler; +} + +void XMLCALL +XML_SetNotationDeclHandler(XML_Parser parser, + XML_NotationDeclHandler handler) +{ + notationDeclHandler = handler; +} + +void XMLCALL +XML_SetNamespaceDeclHandler(XML_Parser parser, + XML_StartNamespaceDeclHandler start, + XML_EndNamespaceDeclHandler end) +{ + startNamespaceDeclHandler = start; + endNamespaceDeclHandler = end; +} + +void XMLCALL +XML_SetStartNamespaceDeclHandler(XML_Parser parser, + XML_StartNamespaceDeclHandler start) { + startNamespaceDeclHandler = start; +} + +void XMLCALL +XML_SetEndNamespaceDeclHandler(XML_Parser parser, + XML_EndNamespaceDeclHandler end) { + endNamespaceDeclHandler = end; +} + +void XMLCALL +XML_SetNotStandaloneHandler(XML_Parser parser, + XML_NotStandaloneHandler handler) +{ + notStandaloneHandler = handler; +} + +void XMLCALL +XML_SetExternalEntityRefHandler(XML_Parser parser, + XML_ExternalEntityRefHandler handler) +{ + externalEntityRefHandler = handler; +} + +void XMLCALL +XML_SetExternalEntityRefHandlerArg(XML_Parser parser, void *arg) +{ + if (arg) + externalEntityRefHandlerArg = (XML_Parser)arg; + else + externalEntityRefHandlerArg = parser; +} + +void XMLCALL +XML_SetSkippedEntityHandler(XML_Parser parser, + XML_SkippedEntityHandler handler) +{ + skippedEntityHandler = handler; +} + +void XMLCALL +XML_SetUnknownEncodingHandler(XML_Parser parser, + XML_UnknownEncodingHandler handler, + void *data) +{ + unknownEncodingHandler = handler; + unknownEncodingHandlerData = data; +} + +void XMLCALL +XML_SetElementDeclHandler(XML_Parser parser, + XML_ElementDeclHandler eldecl) +{ + elementDeclHandler = eldecl; +} + +void XMLCALL +XML_SetAttlistDeclHandler(XML_Parser parser, + XML_AttlistDeclHandler attdecl) +{ + attlistDeclHandler = attdecl; +} + +void XMLCALL +XML_SetEntityDeclHandler(XML_Parser parser, + XML_EntityDeclHandler handler) +{ + entityDeclHandler = handler; +} + +void XMLCALL +XML_SetXmlDeclHandler(XML_Parser parser, + XML_XmlDeclHandler handler) { + xmlDeclHandler = handler; +} + +int XMLCALL +XML_SetParamEntityParsing(XML_Parser parser, + enum XML_ParamEntityParsing peParsing) +{ + /* block after XML_Parse()/XML_ParseBuffer() has been called */ + if (parsing) + return 0; +#ifdef XML_DTD + paramEntityParsing = peParsing; + return 1; +#else + return peParsing == XML_PARAM_ENTITY_PARSING_NEVER; +#endif +} + +enum XML_Status XMLCALL +XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) +{ + if (len == 0) { + if (!isFinal) + return XML_STATUS_OK; + positionPtr = bufferPtr; + errorCode = processor(parser, bufferPtr, parseEndPtr = bufferEnd, 0); + if (errorCode == XML_ERROR_NONE) + return XML_STATUS_OK; + eventEndPtr = eventPtr; + processor = errorProcessor; + return XML_STATUS_ERROR; + } +#ifndef XML_CONTEXT_BYTES + else if (bufferPtr == bufferEnd) { + const char *end; + int nLeftOver; + parseEndByteIndex += len; + positionPtr = s; + if (isFinal) { + errorCode = processor(parser, s, parseEndPtr = s + len, 0); + if (errorCode == XML_ERROR_NONE) + return XML_STATUS_OK; + eventEndPtr = eventPtr; + processor = errorProcessor; + return XML_STATUS_ERROR; + } + errorCode = processor(parser, s, parseEndPtr = s + len, &end); + if (errorCode != XML_ERROR_NONE) { + eventEndPtr = eventPtr; + processor = errorProcessor; + return XML_STATUS_ERROR; + } + XmlUpdatePosition(encoding, positionPtr, end, &position); + positionPtr = end; + nLeftOver = s + len - end; + if (nLeftOver) { + if (buffer == NULL || nLeftOver > bufferLim - buffer) { + /* FIXME avoid integer overflow */ + char *temp; + temp = (buffer == NULL + ? (char *)MALLOC(len * 2) + : (char *)REALLOC(buffer, len * 2)); + if (temp == NULL) { + errorCode = XML_ERROR_NO_MEMORY; + return XML_STATUS_ERROR; + } + buffer = temp; + if (!buffer) { + errorCode = XML_ERROR_NO_MEMORY; + eventPtr = eventEndPtr = NULL; + processor = errorProcessor; + return XML_STATUS_ERROR; + } + bufferLim = buffer + len * 2; + } + memcpy(buffer, end, nLeftOver); + bufferPtr = buffer; + bufferEnd = buffer + nLeftOver; + } + return XML_STATUS_OK; + } +#endif /* not defined XML_CONTEXT_BYTES */ + else { + void *buff = XML_GetBuffer(parser, len); + if (buff == NULL) + return XML_STATUS_ERROR; + else { + memcpy(buff, s, len); + return XML_ParseBuffer(parser, len, isFinal); + } + } +} + +enum XML_Status XMLCALL +XML_ParseBuffer(XML_Parser parser, int len, int isFinal) +{ + const char *start = bufferPtr; + positionPtr = start; + bufferEnd += len; + parseEndByteIndex += len; + errorCode = processor(parser, start, parseEndPtr = bufferEnd, + isFinal ? (const char **)NULL : &bufferPtr); + if (errorCode == XML_ERROR_NONE) { + if (!isFinal) { + XmlUpdatePosition(encoding, positionPtr, bufferPtr, &position); + positionPtr = bufferPtr; + } + return XML_STATUS_OK; + } + else { + eventEndPtr = eventPtr; + processor = errorProcessor; + return XML_STATUS_ERROR; + } +} + +void * XMLCALL +XML_GetBuffer(XML_Parser parser, int len) +{ + if (len > bufferLim - bufferEnd) { + /* FIXME avoid integer overflow */ + int neededSize = len + (bufferEnd - bufferPtr); +#ifdef XML_CONTEXT_BYTES + int keep = bufferPtr - buffer; + + if (keep > XML_CONTEXT_BYTES) + keep = XML_CONTEXT_BYTES; + neededSize += keep; +#endif /* defined XML_CONTEXT_BYTES */ + if (neededSize <= bufferLim - buffer) { +#ifdef XML_CONTEXT_BYTES + if (keep < bufferPtr - buffer) { + int offset = (bufferPtr - buffer) - keep; + memmove(buffer, &buffer[offset], bufferEnd - bufferPtr + keep); + bufferEnd -= offset; + bufferPtr -= offset; + } +#else + memmove(buffer, bufferPtr, bufferEnd - bufferPtr); + bufferEnd = buffer + (bufferEnd - bufferPtr); + bufferPtr = buffer; +#endif /* not defined XML_CONTEXT_BYTES */ + } + else { + char *newBuf; + int bufferSize = bufferLim - bufferPtr; + if (bufferSize == 0) + bufferSize = INIT_BUFFER_SIZE; + do { + bufferSize *= 2; + } while (bufferSize < neededSize); + newBuf = (char *)MALLOC(bufferSize); + if (newBuf == 0) { + errorCode = XML_ERROR_NO_MEMORY; + return NULL; + } + bufferLim = newBuf + bufferSize; +#ifdef XML_CONTEXT_BYTES + if (bufferPtr) { + int keep = bufferPtr - buffer; + if (keep > XML_CONTEXT_BYTES) + keep = XML_CONTEXT_BYTES; + memcpy(newBuf, &bufferPtr[-keep], bufferEnd - bufferPtr + keep); + FREE(buffer); + buffer = newBuf; + bufferEnd = buffer + (bufferEnd - bufferPtr) + keep; + bufferPtr = buffer + keep; + } + else { + bufferEnd = newBuf + (bufferEnd - bufferPtr); + bufferPtr = buffer = newBuf; + } +#else + if (bufferPtr) { + memcpy(newBuf, bufferPtr, bufferEnd - bufferPtr); + FREE(buffer); + } + bufferEnd = newBuf + (bufferEnd - bufferPtr); + bufferPtr = buffer = newBuf; +#endif /* not defined XML_CONTEXT_BYTES */ + } + } + return bufferEnd; +} + +enum XML_Error XMLCALL +XML_GetErrorCode(XML_Parser parser) +{ + return errorCode; +} + +long XMLCALL +XML_GetCurrentByteIndex(XML_Parser parser) +{ + if (eventPtr) + return parseEndByteIndex - (parseEndPtr - eventPtr); + return -1; +} + +int XMLCALL +XML_GetCurrentByteCount(XML_Parser parser) +{ + if (eventEndPtr && eventPtr) + return eventEndPtr - eventPtr; + return 0; +} + +const char * XMLCALL +XML_GetInputContext(XML_Parser parser, int *offset, int *size) +{ +#ifdef XML_CONTEXT_BYTES + if (eventPtr && buffer) { + *offset = eventPtr - buffer; + *size = bufferEnd - buffer; + return buffer; + } +#endif /* defined XML_CONTEXT_BYTES */ + return (char *) 0; +} + +int XMLCALL +XML_GetCurrentLineNumber(XML_Parser parser) +{ + if (eventPtr) { + XmlUpdatePosition(encoding, positionPtr, eventPtr, &position); + positionPtr = eventPtr; + } + return position.lineNumber + 1; +} + +int XMLCALL +XML_GetCurrentColumnNumber(XML_Parser parser) +{ + if (eventPtr) { + XmlUpdatePosition(encoding, positionPtr, eventPtr, &position); + positionPtr = eventPtr; + } + return position.columnNumber; +} + +void XMLCALL +XML_FreeContentModel(XML_Parser parser, XML_Content *model) +{ + FREE(model); +} + +void * XMLCALL +XML_MemMalloc(XML_Parser parser, size_t size) +{ + return MALLOC(size); +} + +void * XMLCALL +XML_MemRealloc(XML_Parser parser, void *ptr, size_t size) +{ + return REALLOC(ptr, size); +} + +void XMLCALL +XML_MemFree(XML_Parser parser, void *ptr) +{ + FREE(ptr); +} + +void XMLCALL +XML_DefaultCurrent(XML_Parser parser) +{ + if (defaultHandler) { + if (openInternalEntities) + reportDefault(parser, + internalEncoding, + openInternalEntities->internalEventPtr, + openInternalEntities->internalEventEndPtr); + else + reportDefault(parser, encoding, eventPtr, eventEndPtr); + } +} + +const XML_LChar * XMLCALL +XML_ErrorString(enum XML_Error code) +{ + static const XML_LChar *message[] = { + 0, + XML_L("out of memory"), + XML_L("syntax error"), + XML_L("no element found"), + XML_L("not well-formed (invalid token)"), + XML_L("unclosed token"), + XML_L("partial character"), + XML_L("mismatched tag"), + XML_L("duplicate attribute"), + XML_L("junk after document element"), + XML_L("illegal parameter entity reference"), + XML_L("undefined entity"), + XML_L("recursive entity reference"), + XML_L("asynchronous entity"), + XML_L("reference to invalid character number"), + XML_L("reference to binary entity"), + XML_L("reference to external entity in attribute"), + XML_L("xml declaration not at start of external entity"), + XML_L("unknown encoding"), + XML_L("encoding specified in XML declaration is incorrect"), + XML_L("unclosed CDATA section"), + XML_L("error in processing external entity reference"), + XML_L("document is not standalone"), + XML_L("unexpected parser state - please send a bug report"), + XML_L("entity declared in parameter entity"), + XML_L("requested feature requires XML_DTD support in Expat"), + XML_L("cannot change setting once parsing has begun"), + XML_L("unbound prefix") + }; + if (code > 0 && code < sizeof(message)/sizeof(message[0])) + return message[code]; + return NULL; +} + +const XML_LChar * XMLCALL +XML_ExpatVersion(void) { + + /* V1 is used to string-ize the version number. However, it would + string-ize the actual version macro *names* unless we get them + substituted before being passed to V1. CPP is defined to expand + a macro, then rescan for more expansions. Thus, we use V2 to expand + the version macros, then CPP will expand the resulting V1() macro + with the correct numerals. */ + /* ### I'm assuming cpp is portable in this respect... */ + +#define V1(a,b,c) XML_L(#a)XML_L(".")XML_L(#b)XML_L(".")XML_L(#c) +#define V2(a,b,c) XML_L("expat_")V1(a,b,c) + + return V2(XML_MAJOR_VERSION, XML_MINOR_VERSION, XML_MICRO_VERSION); + +#undef V1 +#undef V2 +} + +XML_Expat_Version XMLCALL +XML_ExpatVersionInfo(void) +{ + XML_Expat_Version version; + + version.major = XML_MAJOR_VERSION; + version.minor = XML_MINOR_VERSION; + version.micro = XML_MICRO_VERSION; + + return version; +} + +const XML_Feature * XMLCALL +XML_GetFeatureList(void) +{ + static XML_Feature features[] = { + {XML_FEATURE_SIZEOF_XML_CHAR, XML_L("sizeof(XML_Char)"), 0}, + {XML_FEATURE_SIZEOF_XML_LCHAR, XML_L("sizeof(XML_LChar)"), 0}, +#ifdef XML_UNICODE + {XML_FEATURE_UNICODE, XML_L("XML_UNICODE"), 0}, +#endif +#ifdef XML_UNICODE_WCHAR_T + {XML_FEATURE_UNICODE_WCHAR_T, XML_L("XML_UNICODE_WCHAR_T"), 0}, +#endif +#ifdef XML_DTD + {XML_FEATURE_DTD, XML_L("XML_DTD"), 0}, +#endif +#ifdef XML_CONTEXT_BYTES + {XML_FEATURE_CONTEXT_BYTES, XML_L("XML_CONTEXT_BYTES"), + XML_CONTEXT_BYTES}, +#endif +#ifdef XML_MIN_SIZE + {XML_FEATURE_MIN_SIZE, XML_L("XML_MIN_SIZE"), 0}, +#endif + {XML_FEATURE_END, NULL, 0} + }; + + features[0].value = sizeof(XML_Char); + features[1].value = sizeof(XML_LChar); + return features; +} + +/* Initially tag->rawName always points into the parse buffer; + for those TAG instances opened while the current parse buffer was + processed, and not yet closed, we need to store tag->rawName in a more + permanent location, since the parse buffer is about to be discarded. +*/ +static XML_Bool +storeRawNames(XML_Parser parser) +{ + TAG *tag = tagStack; + while (tag) { + int bufSize; + int nameLen = sizeof(XML_Char) * (tag->name.strLen + 1); + char *rawNameBuf = tag->buf + nameLen; + /* Stop if already stored. Since tagStack is a stack, we can stop + at the first entry that has already been copied; everything + below it in the stack is already been accounted for in a + previous call to this function. + */ + if (tag->rawName == rawNameBuf) + break; + /* For re-use purposes we need to ensure that the + size of tag->buf is a multiple of sizeof(XML_Char). + */ + bufSize = nameLen + ROUND_UP(tag->rawNameLength, sizeof(XML_Char)); + if (bufSize > tag->bufEnd - tag->buf) { + char *temp = (char *)REALLOC(tag->buf, bufSize); + if (temp == NULL) + return XML_FALSE; + /* if tag->name.str points to tag->buf (only when namespace + processing is off) then we have to update it + */ + if (tag->name.str == (XML_Char *)tag->buf) + tag->name.str = (XML_Char *)temp; + /* if tag->name.localPart is set (when namespace processing is on) + then update it as well, since it will always point into tag->buf + */ + if (tag->name.localPart) + tag->name.localPart = (XML_Char *)temp + (tag->name.localPart - + (XML_Char *)tag->buf); + tag->buf = temp; + tag->bufEnd = temp + bufSize; + rawNameBuf = temp + nameLen; + } + memcpy(rawNameBuf, tag->rawName, tag->rawNameLength); + tag->rawName = rawNameBuf; + tag = tag->parent; + } + return XML_TRUE; +} + +static enum XML_Error PTRCALL +contentProcessor(XML_Parser parser, + const char *start, + const char *end, + const char **endPtr) +{ + enum XML_Error result = + doContent(parser, 0, encoding, start, end, endPtr); + if (result != XML_ERROR_NONE) + return result; + if (!storeRawNames(parser)) + return XML_ERROR_NO_MEMORY; + return result; +} + +static enum XML_Error PTRCALL +externalEntityInitProcessor(XML_Parser parser, + const char *start, + const char *end, + const char **endPtr) +{ + enum XML_Error result = initializeEncoding(parser); + if (result != XML_ERROR_NONE) + return result; + processor = externalEntityInitProcessor2; + return externalEntityInitProcessor2(parser, start, end, endPtr); +} + +static enum XML_Error PTRCALL +externalEntityInitProcessor2(XML_Parser parser, + const char *start, + const char *end, + const char **endPtr) +{ + const char *next = start; /* XmlContentTok doesn't always set the last arg */ + int tok = XmlContentTok(encoding, start, end, &next); + switch (tok) { + case XML_TOK_BOM: + /* If we are at the end of the buffer, this would cause the next stage, + i.e. externalEntityInitProcessor3, to pass control directly to + doContent (by detecting XML_TOK_NONE) without processing any xml text + declaration - causing the error XML_ERROR_MISPLACED_XML_PI in doContent. + */ + if (next == end && endPtr) { + *endPtr = next; + return XML_ERROR_NONE; + } + start = next; + break; + case XML_TOK_PARTIAL: + if (endPtr) { + *endPtr = start; + return XML_ERROR_NONE; + } + eventPtr = start; + return XML_ERROR_UNCLOSED_TOKEN; + case XML_TOK_PARTIAL_CHAR: + if (endPtr) { + *endPtr = start; + return XML_ERROR_NONE; + } + eventPtr = start; + return XML_ERROR_PARTIAL_CHAR; + } + processor = externalEntityInitProcessor3; + return externalEntityInitProcessor3(parser, start, end, endPtr); +} + +static enum XML_Error PTRCALL +externalEntityInitProcessor3(XML_Parser parser, + const char *start, + const char *end, + const char **endPtr) +{ + const char *next = start; /* XmlContentTok doesn't always set the last arg */ + int tok = XmlContentTok(encoding, start, end, &next); + switch (tok) { + case XML_TOK_XML_DECL: + { + enum XML_Error result = processXmlDecl(parser, 1, start, next); + if (result != XML_ERROR_NONE) + return result; + start = next; + } + break; + case XML_TOK_PARTIAL: + if (endPtr) { + *endPtr = start; + return XML_ERROR_NONE; + } + eventPtr = start; + return XML_ERROR_UNCLOSED_TOKEN; + case XML_TOK_PARTIAL_CHAR: + if (endPtr) { + *endPtr = start; + return XML_ERROR_NONE; + } + eventPtr = start; + return XML_ERROR_PARTIAL_CHAR; + } + processor = externalEntityContentProcessor; + tagLevel = 1; + return externalEntityContentProcessor(parser, start, end, endPtr); +} + +static enum XML_Error PTRCALL +externalEntityContentProcessor(XML_Parser parser, + const char *start, + const char *end, + const char **endPtr) +{ + enum XML_Error result = + doContent(parser, 1, encoding, start, end, endPtr); + if (result != XML_ERROR_NONE) + return result; + if (!storeRawNames(parser)) + return XML_ERROR_NO_MEMORY; + return result; +} + +static enum XML_Error +doContent(XML_Parser parser, + int startTagLevel, + const ENCODING *enc, + const char *s, + const char *end, + const char **nextPtr) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + const char **eventPP; + const char **eventEndPP; + if (enc == encoding) { + eventPP = &eventPtr; + eventEndPP = &eventEndPtr; + } + else { + eventPP = &(openInternalEntities->internalEventPtr); + eventEndPP = &(openInternalEntities->internalEventEndPtr); + } + *eventPP = s; + for (;;) { + const char *next = s; /* XmlContentTok doesn't always set the last arg */ + int tok = XmlContentTok(enc, s, end, &next); + *eventEndPP = next; + switch (tok) { + case XML_TOK_TRAILING_CR: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + *eventEndPP = end; + if (characterDataHandler) { + XML_Char c = 0xA; + characterDataHandler(handlerArg, &c, 1); + } + else if (defaultHandler) + reportDefault(parser, enc, s, end); + if (startTagLevel == 0) + return XML_ERROR_NO_ELEMENTS; + if (tagLevel != startTagLevel) + return XML_ERROR_ASYNC_ENTITY; + return XML_ERROR_NONE; + case XML_TOK_NONE: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + if (startTagLevel > 0) { + if (tagLevel != startTagLevel) + return XML_ERROR_ASYNC_ENTITY; + return XML_ERROR_NONE; + } + return XML_ERROR_NO_ELEMENTS; + case XML_TOK_INVALID: + *eventPP = next; + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_PARTIAL: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + return XML_ERROR_UNCLOSED_TOKEN; + case XML_TOK_PARTIAL_CHAR: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + return XML_ERROR_PARTIAL_CHAR; + case XML_TOK_ENTITY_REF: + { + const XML_Char *name; + ENTITY *entity; + XML_Char ch = (XML_Char) XmlPredefinedEntityName(enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (ch) { + if (characterDataHandler) + characterDataHandler(handlerArg, &ch, 1); + else if (defaultHandler) + reportDefault(parser, enc, s, next); + break; + } + name = poolStoreString(&dtd->pool, enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (!name) + return XML_ERROR_NO_MEMORY; + entity = (ENTITY *)lookup(&dtd->generalEntities, name, 0); + poolDiscard(&dtd->pool); + /* First, determine if a check for an existing declaration is needed; + if yes, check that the entity exists, and that it is internal, + otherwise call the skipped entity or default handler. + */ + if (!dtd->hasParamEntityRefs || dtd->standalone) { + if (!entity) + return XML_ERROR_UNDEFINED_ENTITY; + else if (!entity->is_internal) + return XML_ERROR_ENTITY_DECLARED_IN_PE; + } + else if (!entity) { + if (skippedEntityHandler) + skippedEntityHandler(handlerArg, name, 0); + else if (defaultHandler) + reportDefault(parser, enc, s, next); + break; + } + if (entity->open) + return XML_ERROR_RECURSIVE_ENTITY_REF; + if (entity->notation) + return XML_ERROR_BINARY_ENTITY_REF; + if (entity->textPtr) { + enum XML_Error result; + OPEN_INTERNAL_ENTITY openEntity; + if (!defaultExpandInternalEntities) { + if (skippedEntityHandler) + skippedEntityHandler(handlerArg, entity->name, 0); + else if (defaultHandler) + reportDefault(parser, enc, s, next); + break; + } + entity->open = XML_TRUE; + openEntity.next = openInternalEntities; + openInternalEntities = &openEntity; + openEntity.entity = entity; + openEntity.internalEventPtr = NULL; + openEntity.internalEventEndPtr = NULL; + result = doContent(parser, + tagLevel, + internalEncoding, + (char *)entity->textPtr, + (char *)(entity->textPtr + entity->textLen), + 0); + entity->open = XML_FALSE; + openInternalEntities = openEntity.next; + if (result) + return result; + } + else if (externalEntityRefHandler) { + const XML_Char *context; + entity->open = XML_TRUE; + context = getContext(parser); + entity->open = XML_FALSE; + if (!context) + return XML_ERROR_NO_MEMORY; + if (!externalEntityRefHandler((XML_Parser)externalEntityRefHandlerArg, + context, + entity->base, + entity->systemId, + entity->publicId)) + return XML_ERROR_EXTERNAL_ENTITY_HANDLING; + poolDiscard(&tempPool); + } + else if (defaultHandler) + reportDefault(parser, enc, s, next); + break; + } + case XML_TOK_START_TAG_NO_ATTS: + /* fall through */ + case XML_TOK_START_TAG_WITH_ATTS: + { + TAG *tag; + enum XML_Error result; + XML_Char *toPtr; + if (freeTagList) { + tag = freeTagList; + freeTagList = freeTagList->parent; + } + else { + tag = (TAG *)MALLOC(sizeof(TAG)); + if (!tag) + return XML_ERROR_NO_MEMORY; + tag->buf = (char *)MALLOC(INIT_TAG_BUF_SIZE); + if (!tag->buf) { + FREE(tag); + return XML_ERROR_NO_MEMORY; + } + tag->bufEnd = tag->buf + INIT_TAG_BUF_SIZE; + } + tag->bindings = NULL; + tag->parent = tagStack; + tagStack = tag; + tag->name.localPart = NULL; + tag->name.prefix = NULL; + tag->rawName = s + enc->minBytesPerChar; + tag->rawNameLength = XmlNameLength(enc, tag->rawName); + ++tagLevel; + { + const char *rawNameEnd = tag->rawName + tag->rawNameLength; + const char *fromPtr = tag->rawName; + toPtr = (XML_Char *)tag->buf; + for (;;) { + int bufSize; + int convLen; + XmlConvert(enc, + &fromPtr, rawNameEnd, + (ICHAR **)&toPtr, (ICHAR *)tag->bufEnd - 1); + convLen = toPtr - (XML_Char *)tag->buf; + if (fromPtr == rawNameEnd) { + tag->name.strLen = convLen; + break; + } + bufSize = (tag->bufEnd - tag->buf) << 1; + { + char *temp = (char *)REALLOC(tag->buf, bufSize); + if (temp == NULL) + return XML_ERROR_NO_MEMORY; + tag->buf = temp; + tag->bufEnd = temp + bufSize; + toPtr = (XML_Char *)temp + convLen; + } + } + } + tag->name.str = (XML_Char *)tag->buf; + *toPtr = XML_T('\0'); + result = storeAtts(parser, enc, s, &(tag->name), &(tag->bindings)); + if (result) + return result; + if (startElementHandler) + startElementHandler(handlerArg, tag->name.str, + (const XML_Char **)atts); + else if (defaultHandler) + reportDefault(parser, enc, s, next); + poolClear(&tempPool); + break; + } + case XML_TOK_EMPTY_ELEMENT_NO_ATTS: + /* fall through */ + case XML_TOK_EMPTY_ELEMENT_WITH_ATTS: + { + const char *rawName = s + enc->minBytesPerChar; + enum XML_Error result; + BINDING *bindings = NULL; + XML_Bool noElmHandlers = XML_TRUE; + TAG_NAME name; + name.str = poolStoreString(&tempPool, enc, rawName, + rawName + XmlNameLength(enc, rawName)); + if (!name.str) + return XML_ERROR_NO_MEMORY; + poolFinish(&tempPool); + result = storeAtts(parser, enc, s, &name, &bindings); + if (result) + return result; + poolFinish(&tempPool); + if (startElementHandler) { + startElementHandler(handlerArg, name.str, (const XML_Char **)atts); + noElmHandlers = XML_FALSE; + } + if (endElementHandler) { + if (startElementHandler) + *eventPP = *eventEndPP; + endElementHandler(handlerArg, name.str); + noElmHandlers = XML_FALSE; + } + if (noElmHandlers && defaultHandler) + reportDefault(parser, enc, s, next); + poolClear(&tempPool); + while (bindings) { + BINDING *b = bindings; + if (endNamespaceDeclHandler) + endNamespaceDeclHandler(handlerArg, b->prefix->name); + bindings = bindings->nextTagBinding; + b->nextTagBinding = freeBindingList; + freeBindingList = b; + b->prefix->binding = b->prevPrefixBinding; + } + } + if (tagLevel == 0) + return epilogProcessor(parser, next, end, nextPtr); + break; + case XML_TOK_END_TAG: + if (tagLevel == startTagLevel) + return XML_ERROR_ASYNC_ENTITY; + else { + int len; + const char *rawName; + TAG *tag = tagStack; + tagStack = tag->parent; + tag->parent = freeTagList; + freeTagList = tag; + rawName = s + enc->minBytesPerChar*2; + len = XmlNameLength(enc, rawName); + if (len != tag->rawNameLength + || memcmp(tag->rawName, rawName, len) != 0) { + *eventPP = rawName; + return XML_ERROR_TAG_MISMATCH; + } + --tagLevel; + if (endElementHandler) { + const XML_Char *localPart; + const XML_Char *prefix; + XML_Char *uri; + localPart = tag->name.localPart; + if (ns && localPart) { + /* localPart and prefix may have been overwritten in + tag->name.str, since this points to the binding->uri + buffer which gets re-used; so we have to add them again + */ + uri = (XML_Char *)tag->name.str + tag->name.uriLen; + /* don't need to check for space - already done in storeAtts() */ + while (*localPart) *uri++ = *localPart++; + prefix = (XML_Char *)tag->name.prefix; + if (ns_triplets && prefix) { + *uri++ = namespaceSeparator; + while (*prefix) *uri++ = *prefix++; + } + *uri = XML_T('\0'); + } + endElementHandler(handlerArg, tag->name.str); + } + else if (defaultHandler) + reportDefault(parser, enc, s, next); + while (tag->bindings) { + BINDING *b = tag->bindings; + if (endNamespaceDeclHandler) + endNamespaceDeclHandler(handlerArg, b->prefix->name); + tag->bindings = tag->bindings->nextTagBinding; + b->nextTagBinding = freeBindingList; + freeBindingList = b; + b->prefix->binding = b->prevPrefixBinding; + } + if (tagLevel == 0) + return epilogProcessor(parser, next, end, nextPtr); + } + break; + case XML_TOK_CHAR_REF: + { + int n = XmlCharRefNumber(enc, s); + if (n < 0) + return XML_ERROR_BAD_CHAR_REF; + if (characterDataHandler) { + XML_Char buf[XML_ENCODE_MAX]; + characterDataHandler(handlerArg, buf, XmlEncode(n, (ICHAR *)buf)); + } + else if (defaultHandler) + reportDefault(parser, enc, s, next); + } + break; + case XML_TOK_XML_DECL: + return XML_ERROR_MISPLACED_XML_PI; + case XML_TOK_DATA_NEWLINE: + if (characterDataHandler) { + XML_Char c = 0xA; + characterDataHandler(handlerArg, &c, 1); + } + else if (defaultHandler) + reportDefault(parser, enc, s, next); + break; + case XML_TOK_CDATA_SECT_OPEN: + { + enum XML_Error result; + if (startCdataSectionHandler) + startCdataSectionHandler(handlerArg); +#if 0 + /* Suppose you doing a transformation on a document that involves + changing only the character data. You set up a defaultHandler + and a characterDataHandler. The defaultHandler simply copies + characters through. The characterDataHandler does the + transformation and writes the characters out escaping them as + necessary. This case will fail to work if we leave out the + following two lines (because & and < inside CDATA sections will + be incorrectly escaped). + + However, now we have a start/endCdataSectionHandler, so it seems + easier to let the user deal with this. + */ + else if (characterDataHandler) + characterDataHandler(handlerArg, dataBuf, 0); +#endif + else if (defaultHandler) + reportDefault(parser, enc, s, next); + result = doCdataSection(parser, enc, &next, end, nextPtr); + if (!next) { + processor = cdataSectionProcessor; + return result; + } + } + break; + case XML_TOK_TRAILING_RSQB: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + if (characterDataHandler) { + if (MUST_CONVERT(enc, s)) { + ICHAR *dataPtr = (ICHAR *)dataBuf; + XmlConvert(enc, &s, end, &dataPtr, (ICHAR *)dataBufEnd); + characterDataHandler(handlerArg, dataBuf, + dataPtr - (ICHAR *)dataBuf); + } + else + characterDataHandler(handlerArg, + (XML_Char *)s, + (XML_Char *)end - (XML_Char *)s); + } + else if (defaultHandler) + reportDefault(parser, enc, s, end); + if (startTagLevel == 0) { + *eventPP = end; + return XML_ERROR_NO_ELEMENTS; + } + if (tagLevel != startTagLevel) { + *eventPP = end; + return XML_ERROR_ASYNC_ENTITY; + } + return XML_ERROR_NONE; + case XML_TOK_DATA_CHARS: + if (characterDataHandler) { + if (MUST_CONVERT(enc, s)) { + for (;;) { + ICHAR *dataPtr = (ICHAR *)dataBuf; + XmlConvert(enc, &s, next, &dataPtr, (ICHAR *)dataBufEnd); + *eventEndPP = s; + characterDataHandler(handlerArg, dataBuf, + dataPtr - (ICHAR *)dataBuf); + if (s == next) + break; + *eventPP = s; + } + } + else + characterDataHandler(handlerArg, + (XML_Char *)s, + (XML_Char *)next - (XML_Char *)s); + } + else if (defaultHandler) + reportDefault(parser, enc, s, next); + break; + case XML_TOK_PI: + if (!reportProcessingInstruction(parser, enc, s, next)) + return XML_ERROR_NO_MEMORY; + break; + case XML_TOK_COMMENT: + if (!reportComment(parser, enc, s, next)) + return XML_ERROR_NO_MEMORY; + break; + default: + if (defaultHandler) + reportDefault(parser, enc, s, next); + break; + } + *eventPP = s = next; + } + /* not reached */ +} + +/* Precondition: all arguments must be non-NULL; + Purpose: + - normalize attributes + - check attributes for well-formedness + - generate namespace aware attribute names (URI, prefix) + - build list of attributes for startElementHandler + - default attributes + - process namespace declarations (check and report them) + - generate namespace aware element name (URI, prefix) +*/ +static enum XML_Error +storeAtts(XML_Parser parser, const ENCODING *enc, + const char *attStr, TAG_NAME *tagNamePtr, + BINDING **bindingsPtr) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + ELEMENT_TYPE *elementType; + int nDefaultAtts; + const XML_Char **appAtts; /* the attribute list for the application */ + int attIndex = 0; + int prefixLen; + int i; + int n; + XML_Char *uri; + int nPrefixes = 0; + BINDING *binding; + const XML_Char *localPart; + + /* lookup the element type name */ + elementType = (ELEMENT_TYPE *)lookup(&dtd->elementTypes, tagNamePtr->str,0); + if (!elementType) { + const XML_Char *name = poolCopyString(&dtd->pool, tagNamePtr->str); + if (!name) + return XML_ERROR_NO_MEMORY; + elementType = (ELEMENT_TYPE *)lookup(&dtd->elementTypes, name, + sizeof(ELEMENT_TYPE)); + if (!elementType) + return XML_ERROR_NO_MEMORY; + if (ns && !setElementTypePrefix(parser, elementType)) + return XML_ERROR_NO_MEMORY; + } + nDefaultAtts = elementType->nDefaultAtts; + + /* get the attributes from the tokenizer */ + n = XmlGetAttributes(enc, attStr, attsSize, atts); + if (n + nDefaultAtts > attsSize) { + int oldAttsSize = attsSize; + ATTRIBUTE *temp; + attsSize = n + nDefaultAtts + INIT_ATTS_SIZE; + temp = (ATTRIBUTE *)REALLOC((void *)atts, attsSize * sizeof(ATTRIBUTE)); + if (temp == NULL) + return XML_ERROR_NO_MEMORY; + atts = temp; + if (n > oldAttsSize) + XmlGetAttributes(enc, attStr, n, atts); + } + + appAtts = (const XML_Char **)atts; + for (i = 0; i < n; i++) { + /* add the name and value to the attribute list */ + ATTRIBUTE_ID *attId = getAttributeId(parser, enc, atts[i].name, + atts[i].name + + XmlNameLength(enc, atts[i].name)); + if (!attId) + return XML_ERROR_NO_MEMORY; + /* Detect duplicate attributes by their QNames. This does not work when + namespace processing is turned on and different prefixes for the same + namespace are used. For this case we have a check further down. + */ + if ((attId->name)[-1]) { + if (enc == encoding) + eventPtr = atts[i].name; + return XML_ERROR_DUPLICATE_ATTRIBUTE; + } + (attId->name)[-1] = 1; + appAtts[attIndex++] = attId->name; + if (!atts[i].normalized) { + enum XML_Error result; + XML_Bool isCdata = XML_TRUE; + + /* figure out whether declared as other than CDATA */ + if (attId->maybeTokenized) { + int j; + for (j = 0; j < nDefaultAtts; j++) { + if (attId == elementType->defaultAtts[j].id) { + isCdata = elementType->defaultAtts[j].isCdata; + break; + } + } + } + + /* normalize the attribute value */ + result = storeAttributeValue(parser, enc, isCdata, + atts[i].valuePtr, atts[i].valueEnd, + &tempPool); + if (result) + return result; + appAtts[attIndex] = poolStart(&tempPool); + poolFinish(&tempPool); + } + else { + /* the value did not need normalizing */ + appAtts[attIndex] = poolStoreString(&tempPool, enc, atts[i].valuePtr, + atts[i].valueEnd); + if (appAtts[attIndex] == 0) + return XML_ERROR_NO_MEMORY; + poolFinish(&tempPool); + } + /* handle prefixed attribute names */ + if (attId->prefix) { + if (attId->xmlns) { + /* deal with namespace declarations here */ + enum XML_Error result = addBinding(parser, attId->prefix, attId, + appAtts[attIndex], bindingsPtr); + if (result) + return result; + --attIndex; + } + else { + /* deal with other prefixed names later */ + attIndex++; + nPrefixes++; + (attId->name)[-1] = 2; + } + } + else + attIndex++; + } + + /* set-up for XML_GetSpecifiedAttributeCount and XML_GetIdAttributeIndex */ + nSpecifiedAtts = attIndex; + if (elementType->idAtt && (elementType->idAtt->name)[-1]) { + for (i = 0; i < attIndex; i += 2) + if (appAtts[i] == elementType->idAtt->name) { + idAttIndex = i; + break; + } + } + else + idAttIndex = -1; + + /* do attribute defaulting */ + for (i = 0; i < nDefaultAtts; i++) { + const DEFAULT_ATTRIBUTE *da = elementType->defaultAtts + i; + if (!(da->id->name)[-1] && da->value) { + if (da->id->prefix) { + if (da->id->xmlns) { + enum XML_Error result = addBinding(parser, da->id->prefix, da->id, + da->value, bindingsPtr); + if (result) + return result; + } + else { + (da->id->name)[-1] = 2; + nPrefixes++; + appAtts[attIndex++] = da->id->name; + appAtts[attIndex++] = da->value; + } + } + else { + (da->id->name)[-1] = 1; + appAtts[attIndex++] = da->id->name; + appAtts[attIndex++] = da->value; + } + } + } + appAtts[attIndex] = 0; + + /* expand prefixed attribute names, check for duplicates, + and clear flags that say whether attributes were specified */ + i = 0; + if (nPrefixes) { + int j; /* hash table index */ + unsigned long version = nsAttsVersion; + int nsAttsSize = (int)1 << nsAttsPower; + /* size of hash table must be at least 2 * (# of prefixed attributes) */ + if ((nPrefixes << 1) >> nsAttsPower) { /* true for nsAttsPower = 0 */ + NS_ATT *temp; + /* hash table size must also be a power of 2 and >= 8 */ + while (nPrefixes >> nsAttsPower++); + if (nsAttsPower < 3) + nsAttsPower = 3; + nsAttsSize = (int)1 << nsAttsPower; + temp = (NS_ATT *)REALLOC(nsAtts, nsAttsSize * sizeof(NS_ATT)); + if (!temp) + return XML_ERROR_NO_MEMORY; + nsAtts = temp; + version = 0; /* force re-initialization of nsAtts hash table */ + } + /* using a version flag saves us from initializing nsAtts every time */ + if (!version) { /* initialize version flags when version wraps around */ + version = INIT_ATTS_VERSION; + for (j = nsAttsSize; j != 0; ) + nsAtts[--j].version = version; + } + nsAttsVersion = --version; + + /* expand prefixed names and check for duplicates */ + for (; i < attIndex; i += 2) { + const XML_Char *s = appAtts[i]; + if (s[-1] == 2) { /* prefixed */ + ATTRIBUTE_ID *id; + const BINDING *b; + unsigned long uriHash = 0; + ((XML_Char *)s)[-1] = 0; /* clear flag */ + id = (ATTRIBUTE_ID *)lookup(&dtd->attributeIds, s, 0); + b = id->prefix->binding; + if (!b) + return XML_ERROR_UNBOUND_PREFIX; + + /* as we expand the name we also calculate its hash value */ + for (j = 0; j < b->uriLen; j++) { + const XML_Char c = b->uri[j]; + if (!poolAppendChar(&tempPool, c)) + return XML_ERROR_NO_MEMORY; + uriHash = CHAR_HASH(uriHash, c); + } + while (*s++ != XML_T(':')) + ; + do { /* copies null terminator */ + const XML_Char c = *s; + if (!poolAppendChar(&tempPool, *s)) + return XML_ERROR_NO_MEMORY; + uriHash = CHAR_HASH(uriHash, c); + } while (*s++); + + { /* Check hash table for duplicate of expanded name (uriName). + Derived from code in lookup(HASH_TABLE *table, ...). + */ + unsigned char step = 0; + unsigned long mask = nsAttsSize - 1; + j = uriHash & mask; /* index into hash table */ + while (nsAtts[j].version == version) { + /* for speed we compare stored hash values first */ + if (uriHash == nsAtts[j].hash) { + const XML_Char *s1 = poolStart(&tempPool); + const XML_Char *s2 = nsAtts[j].uriName; + /* s1 is null terminated, but not s2 */ + for (; *s1 == *s2 && *s1 != 0; s1++, s2++); + if (*s1 == 0) + return XML_ERROR_DUPLICATE_ATTRIBUTE; + } + if (!step) + step = PROBE_STEP(uriHash, mask, nsAttsPower); + j < step ? ( j += nsAttsSize - step) : (j -= step); + } + } + + if (ns_triplets) { /* append namespace separator and prefix */ + tempPool.ptr[-1] = namespaceSeparator; + s = b->prefix->name; + do { + if (!poolAppendChar(&tempPool, *s)) + return XML_ERROR_NO_MEMORY; + } while (*s++); + } + + /* store expanded name in attribute list */ + s = poolStart(&tempPool); + poolFinish(&tempPool); + appAtts[i] = s; + + /* fill empty slot with new version, uriName and hash value */ + nsAtts[j].version = version; + nsAtts[j].hash = uriHash; + nsAtts[j].uriName = s; + + if (!--nPrefixes) + break; + } + else /* not prefixed */ + ((XML_Char *)s)[-1] = 0; /* clear flag */ + } + } + /* clear flags for the remaining attributes */ + for (; i < attIndex; i += 2) + ((XML_Char *)(appAtts[i]))[-1] = 0; + for (binding = *bindingsPtr; binding; binding = binding->nextTagBinding) + binding->attId->name[-1] = 0; + + if (!ns) + return XML_ERROR_NONE; + + /* expand the element type name */ + if (elementType->prefix) { + binding = elementType->prefix->binding; + if (!binding) + return XML_ERROR_UNBOUND_PREFIX; + localPart = tagNamePtr->str; + while (*localPart++ != XML_T(':')) + ; + } + else if (dtd->defaultPrefix.binding) { + binding = dtd->defaultPrefix.binding; + localPart = tagNamePtr->str; + } + else + return XML_ERROR_NONE; + prefixLen = 0; + if (ns_triplets && binding->prefix->name) { + for (; binding->prefix->name[prefixLen++];) + ; + } + tagNamePtr->localPart = localPart; + tagNamePtr->uriLen = binding->uriLen; + tagNamePtr->prefix = binding->prefix->name; + tagNamePtr->prefixLen = prefixLen; + for (i = 0; localPart[i++];) + ; + n = i + binding->uriLen + prefixLen; + if (n > binding->uriAlloc) { + TAG *p; + uri = (XML_Char *)MALLOC((n + EXPAND_SPARE) * sizeof(XML_Char)); + if (!uri) + return XML_ERROR_NO_MEMORY; + binding->uriAlloc = n + EXPAND_SPARE; + memcpy(uri, binding->uri, binding->uriLen * sizeof(XML_Char)); + for (p = tagStack; p; p = p->parent) + if (p->name.str == binding->uri) + p->name.str = uri; + FREE(binding->uri); + binding->uri = uri; + } + uri = binding->uri + binding->uriLen; + memcpy(uri, localPart, i * sizeof(XML_Char)); + if (prefixLen) { + uri = uri + (i - 1); + if (namespaceSeparator) + *uri = namespaceSeparator; + memcpy(uri + 1, binding->prefix->name, prefixLen * sizeof(XML_Char)); + } + tagNamePtr->str = binding->uri; + return XML_ERROR_NONE; +} + +/* addBinding() overwrites the value of prefix->binding without checking. + Therefore one must keep track of the old value outside of addBinding(). +*/ +static enum XML_Error +addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, + const XML_Char *uri, BINDING **bindingsPtr) +{ + BINDING *b; + int len; + + /* empty string is only valid when there is no prefix per XML NS 1.0 */ + if (*uri == XML_T('\0') && prefix->name) + return XML_ERROR_SYNTAX; + + for (len = 0; uri[len]; len++) + ; + if (namespaceSeparator) + len++; + if (freeBindingList) { + b = freeBindingList; + if (len > b->uriAlloc) { + XML_Char *temp = (XML_Char *)REALLOC(b->uri, + sizeof(XML_Char) * (len + EXPAND_SPARE)); + if (temp == NULL) + return XML_ERROR_NO_MEMORY; + b->uri = temp; + b->uriAlloc = len + EXPAND_SPARE; + } + freeBindingList = b->nextTagBinding; + } + else { + b = (BINDING *)MALLOC(sizeof(BINDING)); + if (!b) + return XML_ERROR_NO_MEMORY; + b->uri = (XML_Char *)MALLOC(sizeof(XML_Char) * (len + EXPAND_SPARE)); + if (!b->uri) { + FREE(b); + return XML_ERROR_NO_MEMORY; + } + b->uriAlloc = len + EXPAND_SPARE; + } + b->uriLen = len; + memcpy(b->uri, uri, len * sizeof(XML_Char)); + if (namespaceSeparator) + b->uri[len - 1] = namespaceSeparator; + b->prefix = prefix; + b->attId = attId; + b->prevPrefixBinding = prefix->binding; + /* NULL binding when default namespace undeclared */ + if (*uri == XML_T('\0') && prefix == &_dtd->defaultPrefix) + prefix->binding = NULL; + else + prefix->binding = b; + b->nextTagBinding = *bindingsPtr; + *bindingsPtr = b; + if (startNamespaceDeclHandler) + startNamespaceDeclHandler(handlerArg, prefix->name, + prefix->binding ? uri : 0); + return XML_ERROR_NONE; +} + +/* The idea here is to avoid using stack for each CDATA section when + the whole file is parsed with one call. +*/ +static enum XML_Error PTRCALL +cdataSectionProcessor(XML_Parser parser, + const char *start, + const char *end, + const char **endPtr) +{ + enum XML_Error result = doCdataSection(parser, encoding, &start, + end, endPtr); + if (start) { + if (parentParser) { /* we are parsing an external entity */ + processor = externalEntityContentProcessor; + return externalEntityContentProcessor(parser, start, end, endPtr); + } + else { + processor = contentProcessor; + return contentProcessor(parser, start, end, endPtr); + } + } + return result; +} + +/* startPtr gets set to non-null is the section is closed, and to null if + the section is not yet closed. +*/ +static enum XML_Error +doCdataSection(XML_Parser parser, + const ENCODING *enc, + const char **startPtr, + const char *end, + const char **nextPtr) +{ + const char *s = *startPtr; + const char **eventPP; + const char **eventEndPP; + if (enc == encoding) { + eventPP = &eventPtr; + *eventPP = s; + eventEndPP = &eventEndPtr; + } + else { + eventPP = &(openInternalEntities->internalEventPtr); + eventEndPP = &(openInternalEntities->internalEventEndPtr); + } + *eventPP = s; + *startPtr = NULL; + for (;;) { + const char *next; + int tok = XmlCdataSectionTok(enc, s, end, &next); + *eventEndPP = next; + switch (tok) { + case XML_TOK_CDATA_SECT_CLOSE: + if (endCdataSectionHandler) + endCdataSectionHandler(handlerArg); +#if 0 + /* see comment under XML_TOK_CDATA_SECT_OPEN */ + else if (characterDataHandler) + characterDataHandler(handlerArg, dataBuf, 0); +#endif + else if (defaultHandler) + reportDefault(parser, enc, s, next); + *startPtr = next; + return XML_ERROR_NONE; + case XML_TOK_DATA_NEWLINE: + if (characterDataHandler) { + XML_Char c = 0xA; + characterDataHandler(handlerArg, &c, 1); + } + else if (defaultHandler) + reportDefault(parser, enc, s, next); + break; + case XML_TOK_DATA_CHARS: + if (characterDataHandler) { + if (MUST_CONVERT(enc, s)) { + for (;;) { + ICHAR *dataPtr = (ICHAR *)dataBuf; + XmlConvert(enc, &s, next, &dataPtr, (ICHAR *)dataBufEnd); + *eventEndPP = next; + characterDataHandler(handlerArg, dataBuf, + dataPtr - (ICHAR *)dataBuf); + if (s == next) + break; + *eventPP = s; + } + } + else + characterDataHandler(handlerArg, + (XML_Char *)s, + (XML_Char *)next - (XML_Char *)s); + } + else if (defaultHandler) + reportDefault(parser, enc, s, next); + break; + case XML_TOK_INVALID: + *eventPP = next; + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_PARTIAL_CHAR: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + return XML_ERROR_PARTIAL_CHAR; + case XML_TOK_PARTIAL: + case XML_TOK_NONE: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + return XML_ERROR_UNCLOSED_CDATA_SECTION; + default: + *eventPP = next; + return XML_ERROR_UNEXPECTED_STATE; + } + *eventPP = s = next; + } + /* not reached */ +} + +#ifdef XML_DTD + +/* The idea here is to avoid using stack for each IGNORE section when + the whole file is parsed with one call. +*/ +static enum XML_Error PTRCALL +ignoreSectionProcessor(XML_Parser parser, + const char *start, + const char *end, + const char **endPtr) +{ + enum XML_Error result = doIgnoreSection(parser, encoding, &start, + end, endPtr); + if (start) { + processor = prologProcessor; + return prologProcessor(parser, start, end, endPtr); + } + return result; +} + +/* startPtr gets set to non-null is the section is closed, and to null + if the section is not yet closed. +*/ +static enum XML_Error +doIgnoreSection(XML_Parser parser, + const ENCODING *enc, + const char **startPtr, + const char *end, + const char **nextPtr) +{ + const char *next; + int tok; + const char *s = *startPtr; + const char **eventPP; + const char **eventEndPP; + if (enc == encoding) { + eventPP = &eventPtr; + *eventPP = s; + eventEndPP = &eventEndPtr; + } + else { + eventPP = &(openInternalEntities->internalEventPtr); + eventEndPP = &(openInternalEntities->internalEventEndPtr); + } + *eventPP = s; + *startPtr = NULL; + tok = XmlIgnoreSectionTok(enc, s, end, &next); + *eventEndPP = next; + switch (tok) { + case XML_TOK_IGNORE_SECT: + if (defaultHandler) + reportDefault(parser, enc, s, next); + *startPtr = next; + return XML_ERROR_NONE; + case XML_TOK_INVALID: + *eventPP = next; + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_PARTIAL_CHAR: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + return XML_ERROR_PARTIAL_CHAR; + case XML_TOK_PARTIAL: + case XML_TOK_NONE: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + return XML_ERROR_SYNTAX; /* XML_ERROR_UNCLOSED_IGNORE_SECTION */ + default: + *eventPP = next; + return XML_ERROR_UNEXPECTED_STATE; + } + /* not reached */ +} + +#endif /* XML_DTD */ + +static enum XML_Error +initializeEncoding(XML_Parser parser) +{ + const char *s; +#ifdef XML_UNICODE + char encodingBuf[128]; + if (!protocolEncodingName) + s = NULL; + else { + int i; + for (i = 0; protocolEncodingName[i]; i++) { + if (i == sizeof(encodingBuf) - 1 + || (protocolEncodingName[i] & ~0x7f) != 0) { + encodingBuf[0] = '\0'; + break; + } + encodingBuf[i] = (char)protocolEncodingName[i]; + } + encodingBuf[i] = '\0'; + s = encodingBuf; + } +#else + s = protocolEncodingName; +#endif + if ((ns ? XmlInitEncodingNS : XmlInitEncoding)(&initEncoding, &encoding, s)) + return XML_ERROR_NONE; + return handleUnknownEncoding(parser, protocolEncodingName); +} + +static enum XML_Error +processXmlDecl(XML_Parser parser, int isGeneralTextEntity, + const char *s, const char *next) +{ + const char *encodingName = NULL; + const XML_Char *storedEncName = NULL; + const ENCODING *newEncoding = NULL; + const char *version = NULL; + const char *versionend; + const XML_Char *storedversion = NULL; + int standalone = -1; + if (!(ns + ? XmlParseXmlDeclNS + : XmlParseXmlDecl)(isGeneralTextEntity, + encoding, + s, + next, + &eventPtr, + &version, + &versionend, + &encodingName, + &newEncoding, + &standalone)) + return XML_ERROR_SYNTAX; + if (!isGeneralTextEntity && standalone == 1) { + _dtd->standalone = XML_TRUE; +#ifdef XML_DTD + if (paramEntityParsing == XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE) + paramEntityParsing = XML_PARAM_ENTITY_PARSING_NEVER; +#endif /* XML_DTD */ + } + if (xmlDeclHandler) { + if (encodingName != NULL) { + storedEncName = poolStoreString(&temp2Pool, + encoding, + encodingName, + encodingName + + XmlNameLength(encoding, encodingName)); + if (!storedEncName) + return XML_ERROR_NO_MEMORY; + poolFinish(&temp2Pool); + } + if (version) { + storedversion = poolStoreString(&temp2Pool, + encoding, + version, + versionend - encoding->minBytesPerChar); + if (!storedversion) + return XML_ERROR_NO_MEMORY; + } + xmlDeclHandler(handlerArg, storedversion, storedEncName, standalone); + } + else if (defaultHandler) + reportDefault(parser, encoding, s, next); + if (protocolEncodingName == NULL) { + if (newEncoding) { + if (newEncoding->minBytesPerChar != encoding->minBytesPerChar) { + eventPtr = encodingName; + return XML_ERROR_INCORRECT_ENCODING; + } + encoding = newEncoding; + } + else if (encodingName) { + enum XML_Error result; + if (!storedEncName) { + storedEncName = poolStoreString( + &temp2Pool, encoding, encodingName, + encodingName + XmlNameLength(encoding, encodingName)); + if (!storedEncName) + return XML_ERROR_NO_MEMORY; + } + result = handleUnknownEncoding(parser, storedEncName); + poolClear(&temp2Pool); + if (result == XML_ERROR_UNKNOWN_ENCODING) + eventPtr = encodingName; + return result; + } + } + + if (storedEncName || storedversion) + poolClear(&temp2Pool); + + return XML_ERROR_NONE; +} + +static enum XML_Error +handleUnknownEncoding(XML_Parser parser, const XML_Char *encodingName) +{ + if (unknownEncodingHandler) { + XML_Encoding info; + int i; + for (i = 0; i < 256; i++) + info.map[i] = -1; + info.convert = NULL; + info.data = NULL; + info.release = NULL; + if (unknownEncodingHandler(unknownEncodingHandlerData, encodingName, + &info)) { + ENCODING *enc; + unknownEncodingMem = MALLOC(XmlSizeOfUnknownEncoding()); + if (!unknownEncodingMem) { + if (info.release) + info.release(info.data); + return XML_ERROR_NO_MEMORY; + } + enc = (ns + ? XmlInitUnknownEncodingNS + : XmlInitUnknownEncoding)(unknownEncodingMem, + info.map, + info.convert, + info.data); + if (enc) { + unknownEncodingData = info.data; + unknownEncodingRelease = info.release; + encoding = enc; + return XML_ERROR_NONE; + } + } + if (info.release != NULL) + info.release(info.data); + } + return XML_ERROR_UNKNOWN_ENCODING; +} + +static enum XML_Error PTRCALL +prologInitProcessor(XML_Parser parser, + const char *s, + const char *end, + const char **nextPtr) +{ + enum XML_Error result = initializeEncoding(parser); + if (result != XML_ERROR_NONE) + return result; + processor = prologProcessor; + return prologProcessor(parser, s, end, nextPtr); +} + +#ifdef XML_DTD + +static enum XML_Error PTRCALL +externalParEntInitProcessor(XML_Parser parser, + const char *s, + const char *end, + const char **nextPtr) +{ + enum XML_Error result = initializeEncoding(parser); + if (result != XML_ERROR_NONE) + return result; + + /* we know now that XML_Parse(Buffer) has been called, + so we consider the external parameter entity read */ + _dtd->paramEntityRead = XML_TRUE; + + if (prologState.inEntityValue) { + processor = entityValueInitProcessor; + return entityValueInitProcessor(parser, s, end, nextPtr); + } + else { + processor = externalParEntProcessor; + return externalParEntProcessor(parser, s, end, nextPtr); + } +} + +static enum XML_Error PTRCALL +entityValueInitProcessor(XML_Parser parser, + const char *s, + const char *end, + const char **nextPtr) +{ + const char *start = s; + const char *next = s; + int tok; + + for (;;) { + tok = XmlPrologTok(encoding, start, end, &next); + if (tok <= 0) { + if (nextPtr != 0 && tok != XML_TOK_INVALID) { + *nextPtr = s; + return XML_ERROR_NONE; + } + switch (tok) { + case XML_TOK_INVALID: + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_PARTIAL: + return XML_ERROR_UNCLOSED_TOKEN; + case XML_TOK_PARTIAL_CHAR: + return XML_ERROR_PARTIAL_CHAR; + case XML_TOK_NONE: /* start == end */ + default: + break; + } + return storeEntityValue(parser, encoding, s, end); + } + else if (tok == XML_TOK_XML_DECL) { + enum XML_Error result = processXmlDecl(parser, 0, start, next); + if (result != XML_ERROR_NONE) + return result; + if (nextPtr) *nextPtr = next; + /* stop scanning for text declaration - we found one */ + processor = entityValueProcessor; + return entityValueProcessor(parser, next, end, nextPtr); + } + /* If we are at the end of the buffer, this would cause XmlPrologTok to + return XML_TOK_NONE on the next call, which would then cause the + function to exit with *nextPtr set to s - that is what we want for other + tokens, but not for the BOM - we would rather like to skip it; + then, when this routine is entered the next time, XmlPrologTok will + return XML_TOK_INVALID, since the BOM is still in the buffer + */ + else if (tok == XML_TOK_BOM && next == end && nextPtr) { + *nextPtr = next; + return XML_ERROR_NONE; + } + start = next; + } +} + +static enum XML_Error PTRCALL +externalParEntProcessor(XML_Parser parser, + const char *s, + const char *end, + const char **nextPtr) +{ + const char *start = s; + const char *next = s; + int tok; + + tok = XmlPrologTok(encoding, start, end, &next); + if (tok <= 0) { + if (nextPtr != 0 && tok != XML_TOK_INVALID) { + *nextPtr = s; + return XML_ERROR_NONE; + } + switch (tok) { + case XML_TOK_INVALID: + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_PARTIAL: + return XML_ERROR_UNCLOSED_TOKEN; + case XML_TOK_PARTIAL_CHAR: + return XML_ERROR_PARTIAL_CHAR; + case XML_TOK_NONE: /* start == end */ + default: + break; + } + } + /* This would cause the next stage, i.e. doProlog to be passed XML_TOK_BOM. + However, when parsing an external subset, doProlog will not accept a BOM + as valid, and report a syntax error, so we have to skip the BOM + */ + else if (tok == XML_TOK_BOM) { + s = next; + tok = XmlPrologTok(encoding, s, end, &next); + } + + processor = prologProcessor; + return doProlog(parser, encoding, s, end, tok, next, nextPtr); +} + +static enum XML_Error PTRCALL +entityValueProcessor(XML_Parser parser, + const char *s, + const char *end, + const char **nextPtr) +{ + const char *start = s; + const char *next = s; + const ENCODING *enc = encoding; + int tok; + + for (;;) { + tok = XmlPrologTok(enc, start, end, &next); + if (tok <= 0) { + if (nextPtr != 0 && tok != XML_TOK_INVALID) { + *nextPtr = s; + return XML_ERROR_NONE; + } + switch (tok) { + case XML_TOK_INVALID: + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_PARTIAL: + return XML_ERROR_UNCLOSED_TOKEN; + case XML_TOK_PARTIAL_CHAR: + return XML_ERROR_PARTIAL_CHAR; + case XML_TOK_NONE: /* start == end */ + default: + break; + } + return storeEntityValue(parser, enc, s, end); + } + start = next; + } +} + +#endif /* XML_DTD */ + +static enum XML_Error PTRCALL +prologProcessor(XML_Parser parser, + const char *s, + const char *end, + const char **nextPtr) +{ + const char *next = s; + int tok = XmlPrologTok(encoding, s, end, &next); + return doProlog(parser, encoding, s, end, tok, next, nextPtr); +} + +static enum XML_Error +doProlog(XML_Parser parser, + const ENCODING *enc, + const char *s, + const char *end, + int tok, + const char *next, + const char **nextPtr) +{ +#ifdef XML_DTD + static const XML_Char externalSubsetName[] = { '#' , '\0' }; +#endif /* XML_DTD */ + static const XML_Char atypeCDATA[] = { 'C', 'D', 'A', 'T', 'A', '\0' }; + static const XML_Char atypeID[] = { 'I', 'D', '\0' }; + static const XML_Char atypeIDREF[] = { 'I', 'D', 'R', 'E', 'F', '\0' }; + static const XML_Char atypeIDREFS[] = { 'I', 'D', 'R', 'E', 'F', 'S', '\0' }; + static const XML_Char atypeENTITY[] = { 'E', 'N', 'T', 'I', 'T', 'Y', '\0' }; + static const XML_Char atypeENTITIES[] = + { 'E', 'N', 'T', 'I', 'T', 'I', 'E', 'S', '\0' }; + static const XML_Char atypeNMTOKEN[] = { + 'N', 'M', 'T', 'O', 'K', 'E', 'N', '\0' }; + static const XML_Char atypeNMTOKENS[] = { + 'N', 'M', 'T', 'O', 'K', 'E', 'N', 'S', '\0' }; + static const XML_Char notationPrefix[] = { + 'N', 'O', 'T', 'A', 'T', 'I', 'O', 'N', '(', '\0' }; + static const XML_Char enumValueSep[] = { '|', '\0' }; + static const XML_Char enumValueStart[] = { '(', '\0' }; + + DTD * const dtd = _dtd; /* save one level of indirection */ + + const char **eventPP; + const char **eventEndPP; + enum XML_Content_Quant quant; + + if (enc == encoding) { + eventPP = &eventPtr; + eventEndPP = &eventEndPtr; + } + else { + eventPP = &(openInternalEntities->internalEventPtr); + eventEndPP = &(openInternalEntities->internalEventEndPtr); + } + for (;;) { + int role; + XML_Bool handleDefault = XML_TRUE; + *eventPP = s; + *eventEndPP = next; + if (tok <= 0) { + if (nextPtr != 0 && tok != XML_TOK_INVALID) { + *nextPtr = s; + return XML_ERROR_NONE; + } + switch (tok) { + case XML_TOK_INVALID: + *eventPP = next; + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_PARTIAL: + return XML_ERROR_UNCLOSED_TOKEN; + case XML_TOK_PARTIAL_CHAR: + return XML_ERROR_PARTIAL_CHAR; + case XML_TOK_NONE: +#ifdef XML_DTD + if (enc != encoding) + return XML_ERROR_NONE; + if (isParamEntity) { + if (XmlTokenRole(&prologState, XML_TOK_NONE, end, end, enc) + == XML_ROLE_ERROR) + return XML_ERROR_SYNTAX; + return XML_ERROR_NONE; + } +#endif /* XML_DTD */ + return XML_ERROR_NO_ELEMENTS; + default: + tok = -tok; + next = end; + break; + } + } + role = XmlTokenRole(&prologState, tok, s, next, enc); + switch (role) { + case XML_ROLE_XML_DECL: + { + enum XML_Error result = processXmlDecl(parser, 0, s, next); + if (result != XML_ERROR_NONE) + return result; + enc = encoding; + handleDefault = XML_FALSE; + } + break; + case XML_ROLE_DOCTYPE_NAME: + if (startDoctypeDeclHandler) { + doctypeName = poolStoreString(&tempPool, enc, s, next); + if (!doctypeName) + return XML_ERROR_NO_MEMORY; + poolFinish(&tempPool); + doctypePubid = NULL; + handleDefault = XML_FALSE; + } + doctypeSysid = NULL; /* always initialize to NULL */ + break; + case XML_ROLE_DOCTYPE_INTERNAL_SUBSET: + if (startDoctypeDeclHandler) { + startDoctypeDeclHandler(handlerArg, doctypeName, doctypeSysid, + doctypePubid, 1); + doctypeName = NULL; + poolClear(&tempPool); + handleDefault = XML_FALSE; + } + break; +#ifdef XML_DTD + case XML_ROLE_TEXT_DECL: + { + enum XML_Error result = processXmlDecl(parser, 1, s, next); + if (result != XML_ERROR_NONE) + return result; + enc = encoding; + handleDefault = XML_FALSE; + } + break; +#endif /* XML_DTD */ + case XML_ROLE_DOCTYPE_PUBLIC_ID: +#ifdef XML_DTD + useForeignDTD = XML_FALSE; +#endif /* XML_DTD */ + dtd->hasParamEntityRefs = XML_TRUE; + if (startDoctypeDeclHandler) { + doctypePubid = poolStoreString(&tempPool, enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (!doctypePubid) + return XML_ERROR_NO_MEMORY; + poolFinish(&tempPool); + handleDefault = XML_FALSE; + } +#ifdef XML_DTD + declEntity = (ENTITY *)lookup(&dtd->paramEntities, + externalSubsetName, + sizeof(ENTITY)); + if (!declEntity) + return XML_ERROR_NO_MEMORY; +#endif /* XML_DTD */ + /* fall through */ + case XML_ROLE_ENTITY_PUBLIC_ID: + if (!XmlIsPublicId(enc, s, next, eventPP)) + return XML_ERROR_SYNTAX; + if (dtd->keepProcessing && declEntity) { + XML_Char *tem = poolStoreString(&dtd->pool, + enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (!tem) + return XML_ERROR_NO_MEMORY; + normalizePublicId(tem); + declEntity->publicId = tem; + poolFinish(&dtd->pool); + if (entityDeclHandler) + handleDefault = XML_FALSE; + } + break; + case XML_ROLE_DOCTYPE_CLOSE: + if (doctypeName) { + startDoctypeDeclHandler(handlerArg, doctypeName, + doctypeSysid, doctypePubid, 0); + poolClear(&tempPool); + handleDefault = XML_FALSE; + } + /* doctypeSysid will be non-NULL in the case of a previous + XML_ROLE_DOCTYPE_SYSTEM_ID, even if startDoctypeDeclHandler + was not set, indicating an external subset + */ +#ifdef XML_DTD + if (doctypeSysid || useForeignDTD) { + dtd->hasParamEntityRefs = XML_TRUE; /* when docTypeSysid == NULL */ + if (paramEntityParsing && externalEntityRefHandler) { + ENTITY *entity = (ENTITY *)lookup(&dtd->paramEntities, + externalSubsetName, + sizeof(ENTITY)); + if (!entity) + return XML_ERROR_NO_MEMORY; + if (useForeignDTD) + entity->base = curBase; + dtd->paramEntityRead = XML_FALSE; + if (!externalEntityRefHandler(externalEntityRefHandlerArg, + 0, + entity->base, + entity->systemId, + entity->publicId)) + return XML_ERROR_EXTERNAL_ENTITY_HANDLING; + if (dtd->paramEntityRead && + !dtd->standalone && + notStandaloneHandler && + !notStandaloneHandler(handlerArg)) + return XML_ERROR_NOT_STANDALONE; + /* end of DTD - no need to update dtd->keepProcessing */ + } + useForeignDTD = XML_FALSE; + } +#endif /* XML_DTD */ + if (endDoctypeDeclHandler) { + endDoctypeDeclHandler(handlerArg); + handleDefault = XML_FALSE; + } + break; + case XML_ROLE_INSTANCE_START: +#ifdef XML_DTD + /* if there is no DOCTYPE declaration then now is the + last chance to read the foreign DTD + */ + if (useForeignDTD) { + dtd->hasParamEntityRefs = XML_TRUE; + if (paramEntityParsing && externalEntityRefHandler) { + ENTITY *entity = (ENTITY *)lookup(&dtd->paramEntities, + externalSubsetName, + sizeof(ENTITY)); + if (!entity) + return XML_ERROR_NO_MEMORY; + entity->base = curBase; + dtd->paramEntityRead = XML_FALSE; + if (!externalEntityRefHandler(externalEntityRefHandlerArg, + 0, + entity->base, + entity->systemId, + entity->publicId)) + return XML_ERROR_EXTERNAL_ENTITY_HANDLING; + if (dtd->paramEntityRead && + !dtd->standalone && + notStandaloneHandler && + !notStandaloneHandler(handlerArg)) + return XML_ERROR_NOT_STANDALONE; + /* end of DTD - no need to update dtd->keepProcessing */ + } + } +#endif /* XML_DTD */ + processor = contentProcessor; + return contentProcessor(parser, s, end, nextPtr); + case XML_ROLE_ATTLIST_ELEMENT_NAME: + declElementType = getElementType(parser, enc, s, next); + if (!declElementType) + return XML_ERROR_NO_MEMORY; + goto checkAttListDeclHandler; + case XML_ROLE_ATTRIBUTE_NAME: + declAttributeId = getAttributeId(parser, enc, s, next); + if (!declAttributeId) + return XML_ERROR_NO_MEMORY; + declAttributeIsCdata = XML_FALSE; + declAttributeType = NULL; + declAttributeIsId = XML_FALSE; + goto checkAttListDeclHandler; + case XML_ROLE_ATTRIBUTE_TYPE_CDATA: + declAttributeIsCdata = XML_TRUE; + declAttributeType = atypeCDATA; + goto checkAttListDeclHandler; + case XML_ROLE_ATTRIBUTE_TYPE_ID: + declAttributeIsId = XML_TRUE; + declAttributeType = atypeID; + goto checkAttListDeclHandler; + case XML_ROLE_ATTRIBUTE_TYPE_IDREF: + declAttributeType = atypeIDREF; + goto checkAttListDeclHandler; + case XML_ROLE_ATTRIBUTE_TYPE_IDREFS: + declAttributeType = atypeIDREFS; + goto checkAttListDeclHandler; + case XML_ROLE_ATTRIBUTE_TYPE_ENTITY: + declAttributeType = atypeENTITY; + goto checkAttListDeclHandler; + case XML_ROLE_ATTRIBUTE_TYPE_ENTITIES: + declAttributeType = atypeENTITIES; + goto checkAttListDeclHandler; + case XML_ROLE_ATTRIBUTE_TYPE_NMTOKEN: + declAttributeType = atypeNMTOKEN; + goto checkAttListDeclHandler; + case XML_ROLE_ATTRIBUTE_TYPE_NMTOKENS: + declAttributeType = atypeNMTOKENS; + checkAttListDeclHandler: + if (dtd->keepProcessing && attlistDeclHandler) + handleDefault = XML_FALSE; + break; + case XML_ROLE_ATTRIBUTE_ENUM_VALUE: + case XML_ROLE_ATTRIBUTE_NOTATION_VALUE: + if (dtd->keepProcessing && attlistDeclHandler) { + const XML_Char *prefix; + if (declAttributeType) { + prefix = enumValueSep; + } + else { + prefix = (role == XML_ROLE_ATTRIBUTE_NOTATION_VALUE + ? notationPrefix + : enumValueStart); + } + if (!poolAppendString(&tempPool, prefix)) + return XML_ERROR_NO_MEMORY; + if (!poolAppend(&tempPool, enc, s, next)) + return XML_ERROR_NO_MEMORY; + declAttributeType = tempPool.start; + handleDefault = XML_FALSE; + } + break; + case XML_ROLE_IMPLIED_ATTRIBUTE_VALUE: + case XML_ROLE_REQUIRED_ATTRIBUTE_VALUE: + if (dtd->keepProcessing) { + if (!defineAttribute(declElementType, declAttributeId, + declAttributeIsCdata, declAttributeIsId, + 0, parser)) + return XML_ERROR_NO_MEMORY; + if (attlistDeclHandler && declAttributeType) { + if (*declAttributeType == XML_T('(') + || (*declAttributeType == XML_T('N') + && declAttributeType[1] == XML_T('O'))) { + /* Enumerated or Notation type */ + if (!poolAppendChar(&tempPool, XML_T(')')) + || !poolAppendChar(&tempPool, XML_T('\0'))) + return XML_ERROR_NO_MEMORY; + declAttributeType = tempPool.start; + poolFinish(&tempPool); + } + *eventEndPP = s; + attlistDeclHandler(handlerArg, declElementType->name, + declAttributeId->name, declAttributeType, + 0, role == XML_ROLE_REQUIRED_ATTRIBUTE_VALUE); + poolClear(&tempPool); + handleDefault = XML_FALSE; + } + } + break; + case XML_ROLE_DEFAULT_ATTRIBUTE_VALUE: + case XML_ROLE_FIXED_ATTRIBUTE_VALUE: + if (dtd->keepProcessing) { + const XML_Char *attVal; + enum XML_Error result = + storeAttributeValue(parser, enc, declAttributeIsCdata, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar, + &dtd->pool); + if (result) + return result; + attVal = poolStart(&dtd->pool); + poolFinish(&dtd->pool); + /* ID attributes aren't allowed to have a default */ + if (!defineAttribute(declElementType, declAttributeId, + declAttributeIsCdata, XML_FALSE, attVal, parser)) + return XML_ERROR_NO_MEMORY; + if (attlistDeclHandler && declAttributeType) { + if (*declAttributeType == XML_T('(') + || (*declAttributeType == XML_T('N') + && declAttributeType[1] == XML_T('O'))) { + /* Enumerated or Notation type */ + if (!poolAppendChar(&tempPool, XML_T(')')) + || !poolAppendChar(&tempPool, XML_T('\0'))) + return XML_ERROR_NO_MEMORY; + declAttributeType = tempPool.start; + poolFinish(&tempPool); + } + *eventEndPP = s; + attlistDeclHandler(handlerArg, declElementType->name, + declAttributeId->name, declAttributeType, + attVal, + role == XML_ROLE_FIXED_ATTRIBUTE_VALUE); + poolClear(&tempPool); + handleDefault = XML_FALSE; + } + } + break; + case XML_ROLE_ENTITY_VALUE: + if (dtd->keepProcessing) { + enum XML_Error result = storeEntityValue(parser, enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (declEntity) { + declEntity->textPtr = poolStart(&dtd->entityValuePool); + declEntity->textLen = poolLength(&dtd->entityValuePool); + poolFinish(&dtd->entityValuePool); + if (entityDeclHandler) { + *eventEndPP = s; + entityDeclHandler(handlerArg, + declEntity->name, + declEntity->is_param, + declEntity->textPtr, + declEntity->textLen, + curBase, 0, 0, 0); + handleDefault = XML_FALSE; + } + } + else + poolDiscard(&dtd->entityValuePool); + if (result != XML_ERROR_NONE) + return result; + } + break; + case XML_ROLE_DOCTYPE_SYSTEM_ID: +#ifdef XML_DTD + useForeignDTD = XML_FALSE; +#endif /* XML_DTD */ + dtd->hasParamEntityRefs = XML_TRUE; + if (startDoctypeDeclHandler) { + doctypeSysid = poolStoreString(&tempPool, enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (doctypeSysid == NULL) + return XML_ERROR_NO_MEMORY; + poolFinish(&tempPool); + handleDefault = XML_FALSE; + } +#ifdef XML_DTD + else + /* use externalSubsetName to make doctypeSysid non-NULL + for the case where no startDoctypeDeclHandler is set */ + doctypeSysid = externalSubsetName; +#endif /* XML_DTD */ + if (!dtd->standalone +#ifdef XML_DTD + && !paramEntityParsing +#endif /* XML_DTD */ + && notStandaloneHandler + && !notStandaloneHandler(handlerArg)) + return XML_ERROR_NOT_STANDALONE; +#ifndef XML_DTD + break; +#else /* XML_DTD */ + if (!declEntity) { + declEntity = (ENTITY *)lookup(&dtd->paramEntities, + externalSubsetName, + sizeof(ENTITY)); + if (!declEntity) + return XML_ERROR_NO_MEMORY; + declEntity->publicId = NULL; + } + /* fall through */ +#endif /* XML_DTD */ + case XML_ROLE_ENTITY_SYSTEM_ID: + if (dtd->keepProcessing && declEntity) { + declEntity->systemId = poolStoreString(&dtd->pool, enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (!declEntity->systemId) + return XML_ERROR_NO_MEMORY; + declEntity->base = curBase; + poolFinish(&dtd->pool); + if (entityDeclHandler) + handleDefault = XML_FALSE; + } + break; + case XML_ROLE_ENTITY_COMPLETE: + if (dtd->keepProcessing && declEntity && entityDeclHandler) { + *eventEndPP = s; + entityDeclHandler(handlerArg, + declEntity->name, + declEntity->is_param, + 0,0, + declEntity->base, + declEntity->systemId, + declEntity->publicId, + 0); + handleDefault = XML_FALSE; + } + break; + case XML_ROLE_ENTITY_NOTATION_NAME: + if (dtd->keepProcessing && declEntity) { + declEntity->notation = poolStoreString(&dtd->pool, enc, s, next); + if (!declEntity->notation) + return XML_ERROR_NO_MEMORY; + poolFinish(&dtd->pool); + if (unparsedEntityDeclHandler) { + *eventEndPP = s; + unparsedEntityDeclHandler(handlerArg, + declEntity->name, + declEntity->base, + declEntity->systemId, + declEntity->publicId, + declEntity->notation); + handleDefault = XML_FALSE; + } + else if (entityDeclHandler) { + *eventEndPP = s; + entityDeclHandler(handlerArg, + declEntity->name, + 0,0,0, + declEntity->base, + declEntity->systemId, + declEntity->publicId, + declEntity->notation); + handleDefault = XML_FALSE; + } + } + break; + case XML_ROLE_GENERAL_ENTITY_NAME: + { + if (XmlPredefinedEntityName(enc, s, next)) { + declEntity = NULL; + break; + } + if (dtd->keepProcessing) { + const XML_Char *name = poolStoreString(&dtd->pool, enc, s, next); + if (!name) + return XML_ERROR_NO_MEMORY; + declEntity = (ENTITY *)lookup(&dtd->generalEntities, name, + sizeof(ENTITY)); + if (!declEntity) + return XML_ERROR_NO_MEMORY; + if (declEntity->name != name) { + poolDiscard(&dtd->pool); + declEntity = NULL; + } + else { + poolFinish(&dtd->pool); + declEntity->publicId = NULL; + declEntity->is_param = XML_FALSE; + /* if we have a parent parser or are reading an internal parameter + entity, then the entity declaration is not considered "internal" + */ + declEntity->is_internal = !(parentParser || openInternalEntities); + if (entityDeclHandler) + handleDefault = XML_FALSE; + } + } + else { + poolDiscard(&dtd->pool); + declEntity = NULL; + } + } + break; + case XML_ROLE_PARAM_ENTITY_NAME: +#ifdef XML_DTD + if (dtd->keepProcessing) { + const XML_Char *name = poolStoreString(&dtd->pool, enc, s, next); + if (!name) + return XML_ERROR_NO_MEMORY; + declEntity = (ENTITY *)lookup(&dtd->paramEntities, + name, sizeof(ENTITY)); + if (!declEntity) + return XML_ERROR_NO_MEMORY; + if (declEntity->name != name) { + poolDiscard(&dtd->pool); + declEntity = NULL; + } + else { + poolFinish(&dtd->pool); + declEntity->publicId = NULL; + declEntity->is_param = XML_TRUE; + /* if we have a parent parser or are reading an internal parameter + entity, then the entity declaration is not considered "internal" + */ + declEntity->is_internal = !(parentParser || openInternalEntities); + if (entityDeclHandler) + handleDefault = XML_FALSE; + } + } + else { + poolDiscard(&dtd->pool); + declEntity = NULL; + } +#else /* not XML_DTD */ + declEntity = NULL; +#endif /* XML_DTD */ + break; + case XML_ROLE_NOTATION_NAME: + declNotationPublicId = NULL; + declNotationName = NULL; + if (notationDeclHandler) { + declNotationName = poolStoreString(&tempPool, enc, s, next); + if (!declNotationName) + return XML_ERROR_NO_MEMORY; + poolFinish(&tempPool); + handleDefault = XML_FALSE; + } + break; + case XML_ROLE_NOTATION_PUBLIC_ID: + if (!XmlIsPublicId(enc, s, next, eventPP)) + return XML_ERROR_SYNTAX; + if (declNotationName) { /* means notationDeclHandler != NULL */ + XML_Char *tem = poolStoreString(&tempPool, + enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (!tem) + return XML_ERROR_NO_MEMORY; + normalizePublicId(tem); + declNotationPublicId = tem; + poolFinish(&tempPool); + handleDefault = XML_FALSE; + } + break; + case XML_ROLE_NOTATION_SYSTEM_ID: + if (declNotationName && notationDeclHandler) { + const XML_Char *systemId + = poolStoreString(&tempPool, enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (!systemId) + return XML_ERROR_NO_MEMORY; + *eventEndPP = s; + notationDeclHandler(handlerArg, + declNotationName, + curBase, + systemId, + declNotationPublicId); + handleDefault = XML_FALSE; + } + poolClear(&tempPool); + break; + case XML_ROLE_NOTATION_NO_SYSTEM_ID: + if (declNotationPublicId && notationDeclHandler) { + *eventEndPP = s; + notationDeclHandler(handlerArg, + declNotationName, + curBase, + 0, + declNotationPublicId); + handleDefault = XML_FALSE; + } + poolClear(&tempPool); + break; + case XML_ROLE_ERROR: + switch (tok) { + case XML_TOK_PARAM_ENTITY_REF: + return XML_ERROR_PARAM_ENTITY_REF; + case XML_TOK_XML_DECL: + return XML_ERROR_MISPLACED_XML_PI; + default: + return XML_ERROR_SYNTAX; + } +#ifdef XML_DTD + case XML_ROLE_IGNORE_SECT: + { + enum XML_Error result; + if (defaultHandler) + reportDefault(parser, enc, s, next); + handleDefault = XML_FALSE; + result = doIgnoreSection(parser, enc, &next, end, nextPtr); + if (!next) { + processor = ignoreSectionProcessor; + return result; + } + } + break; +#endif /* XML_DTD */ + case XML_ROLE_GROUP_OPEN: + if (prologState.level >= groupSize) { + if (groupSize) { + char *temp = (char *)REALLOC(groupConnector, groupSize *= 2); + if (temp == NULL) + return XML_ERROR_NO_MEMORY; + groupConnector = temp; + if (dtd->scaffIndex) { + int *temp = (int *)REALLOC(dtd->scaffIndex, + groupSize * sizeof(int)); + if (temp == NULL) + return XML_ERROR_NO_MEMORY; + dtd->scaffIndex = temp; + } + } + else { + groupConnector = (char *)MALLOC(groupSize = 32); + if (!groupConnector) + return XML_ERROR_NO_MEMORY; + } + } + groupConnector[prologState.level] = 0; + if (dtd->in_eldecl) { + int myindex = nextScaffoldPart(parser); + if (myindex < 0) + return XML_ERROR_NO_MEMORY; + dtd->scaffIndex[dtd->scaffLevel] = myindex; + dtd->scaffLevel++; + dtd->scaffold[myindex].type = XML_CTYPE_SEQ; + if (elementDeclHandler) + handleDefault = XML_FALSE; + } + break; + case XML_ROLE_GROUP_SEQUENCE: + if (groupConnector[prologState.level] == '|') + return XML_ERROR_SYNTAX; + groupConnector[prologState.level] = ','; + if (dtd->in_eldecl && elementDeclHandler) + handleDefault = XML_FALSE; + break; + case XML_ROLE_GROUP_CHOICE: + if (groupConnector[prologState.level] == ',') + return XML_ERROR_SYNTAX; + if (dtd->in_eldecl + && !groupConnector[prologState.level] + && (dtd->scaffold[dtd->scaffIndex[dtd->scaffLevel - 1]].type + != XML_CTYPE_MIXED) + ) { + dtd->scaffold[dtd->scaffIndex[dtd->scaffLevel - 1]].type + = XML_CTYPE_CHOICE; + if (elementDeclHandler) + handleDefault = XML_FALSE; + } + groupConnector[prologState.level] = '|'; + break; + case XML_ROLE_PARAM_ENTITY_REF: +#ifdef XML_DTD + case XML_ROLE_INNER_PARAM_ENTITY_REF: + /* PE references in internal subset are + not allowed within declarations */ + if (prologState.documentEntity && + role == XML_ROLE_INNER_PARAM_ENTITY_REF) + return XML_ERROR_PARAM_ENTITY_REF; + dtd->hasParamEntityRefs = XML_TRUE; + if (!paramEntityParsing) + dtd->keepProcessing = dtd->standalone; + else { + const XML_Char *name; + ENTITY *entity; + name = poolStoreString(&dtd->pool, enc, + s + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (!name) + return XML_ERROR_NO_MEMORY; + entity = (ENTITY *)lookup(&dtd->paramEntities, name, 0); + poolDiscard(&dtd->pool); + /* first, determine if a check for an existing declaration is needed; + if yes, check that the entity exists, and that it is internal, + otherwise call the skipped entity handler + */ + if (prologState.documentEntity && + (dtd->standalone + ? !openInternalEntities + : !dtd->hasParamEntityRefs)) { + if (!entity) + return XML_ERROR_UNDEFINED_ENTITY; + else if (!entity->is_internal) + return XML_ERROR_ENTITY_DECLARED_IN_PE; + } + else if (!entity) { + dtd->keepProcessing = dtd->standalone; + /* cannot report skipped entities in declarations */ + if ((role == XML_ROLE_PARAM_ENTITY_REF) && skippedEntityHandler) { + skippedEntityHandler(handlerArg, name, 1); + handleDefault = XML_FALSE; + } + break; + } + if (entity->open) + return XML_ERROR_RECURSIVE_ENTITY_REF; + if (entity->textPtr) { + enum XML_Error result; + result = processInternalParamEntity(parser, entity); + if (result != XML_ERROR_NONE) + return result; + handleDefault = XML_FALSE; + break; + } + if (externalEntityRefHandler) { + dtd->paramEntityRead = XML_FALSE; + entity->open = XML_TRUE; + if (!externalEntityRefHandler(externalEntityRefHandlerArg, + 0, + entity->base, + entity->systemId, + entity->publicId)) { + entity->open = XML_FALSE; + return XML_ERROR_EXTERNAL_ENTITY_HANDLING; + } + entity->open = XML_FALSE; + handleDefault = XML_FALSE; + if (!dtd->paramEntityRead) { + dtd->keepProcessing = dtd->standalone; + break; + } + } + else { + dtd->keepProcessing = dtd->standalone; + break; + } + } +#endif /* XML_DTD */ + if (!dtd->standalone && + notStandaloneHandler && + !notStandaloneHandler(handlerArg)) + return XML_ERROR_NOT_STANDALONE; + break; + + /* Element declaration stuff */ + + case XML_ROLE_ELEMENT_NAME: + if (elementDeclHandler) { + declElementType = getElementType(parser, enc, s, next); + if (!declElementType) + return XML_ERROR_NO_MEMORY; + dtd->scaffLevel = 0; + dtd->scaffCount = 0; + dtd->in_eldecl = XML_TRUE; + handleDefault = XML_FALSE; + } + break; + + case XML_ROLE_CONTENT_ANY: + case XML_ROLE_CONTENT_EMPTY: + if (dtd->in_eldecl) { + if (elementDeclHandler) { + XML_Content * content = (XML_Content *) MALLOC(sizeof(XML_Content)); + if (!content) + return XML_ERROR_NO_MEMORY; + content->quant = XML_CQUANT_NONE; + content->name = NULL; + content->numchildren = 0; + content->children = NULL; + content->type = ((role == XML_ROLE_CONTENT_ANY) ? + XML_CTYPE_ANY : + XML_CTYPE_EMPTY); + *eventEndPP = s; + elementDeclHandler(handlerArg, declElementType->name, content); + handleDefault = XML_FALSE; + } + dtd->in_eldecl = XML_FALSE; + } + break; + + case XML_ROLE_CONTENT_PCDATA: + if (dtd->in_eldecl) { + dtd->scaffold[dtd->scaffIndex[dtd->scaffLevel - 1]].type + = XML_CTYPE_MIXED; + if (elementDeclHandler) + handleDefault = XML_FALSE; + } + break; + + case XML_ROLE_CONTENT_ELEMENT: + quant = XML_CQUANT_NONE; + goto elementContent; + case XML_ROLE_CONTENT_ELEMENT_OPT: + quant = XML_CQUANT_OPT; + goto elementContent; + case XML_ROLE_CONTENT_ELEMENT_REP: + quant = XML_CQUANT_REP; + goto elementContent; + case XML_ROLE_CONTENT_ELEMENT_PLUS: + quant = XML_CQUANT_PLUS; + elementContent: + if (dtd->in_eldecl) { + ELEMENT_TYPE *el; + const XML_Char *name; + int nameLen; + const char *nxt = (quant == XML_CQUANT_NONE + ? next + : next - enc->minBytesPerChar); + int myindex = nextScaffoldPart(parser); + if (myindex < 0) + return XML_ERROR_NO_MEMORY; + dtd->scaffold[myindex].type = XML_CTYPE_NAME; + dtd->scaffold[myindex].quant = quant; + el = getElementType(parser, enc, s, nxt); + if (!el) + return XML_ERROR_NO_MEMORY; + name = el->name; + dtd->scaffold[myindex].name = name; + nameLen = 0; + for (; name[nameLen++]; ); + dtd->contentStringLen += nameLen; + if (elementDeclHandler) + handleDefault = XML_FALSE; + } + break; + + case XML_ROLE_GROUP_CLOSE: + quant = XML_CQUANT_NONE; + goto closeGroup; + case XML_ROLE_GROUP_CLOSE_OPT: + quant = XML_CQUANT_OPT; + goto closeGroup; + case XML_ROLE_GROUP_CLOSE_REP: + quant = XML_CQUANT_REP; + goto closeGroup; + case XML_ROLE_GROUP_CLOSE_PLUS: + quant = XML_CQUANT_PLUS; + closeGroup: + if (dtd->in_eldecl) { + if (elementDeclHandler) + handleDefault = XML_FALSE; + dtd->scaffLevel--; + dtd->scaffold[dtd->scaffIndex[dtd->scaffLevel]].quant = quant; + if (dtd->scaffLevel == 0) { + if (!handleDefault) { + XML_Content *model = build_model(parser); + if (!model) + return XML_ERROR_NO_MEMORY; + *eventEndPP = s; + elementDeclHandler(handlerArg, declElementType->name, model); + } + dtd->in_eldecl = XML_FALSE; + dtd->contentStringLen = 0; + } + } + break; + /* End element declaration stuff */ + + case XML_ROLE_PI: + if (!reportProcessingInstruction(parser, enc, s, next)) + return XML_ERROR_NO_MEMORY; + handleDefault = XML_FALSE; + break; + case XML_ROLE_COMMENT: + if (!reportComment(parser, enc, s, next)) + return XML_ERROR_NO_MEMORY; + handleDefault = XML_FALSE; + break; + case XML_ROLE_NONE: + switch (tok) { + case XML_TOK_BOM: + handleDefault = XML_FALSE; + break; + } + break; + case XML_ROLE_DOCTYPE_NONE: + if (startDoctypeDeclHandler) + handleDefault = XML_FALSE; + break; + case XML_ROLE_ENTITY_NONE: + if (dtd->keepProcessing && entityDeclHandler) + handleDefault = XML_FALSE; + break; + case XML_ROLE_NOTATION_NONE: + if (notationDeclHandler) + handleDefault = XML_FALSE; + break; + case XML_ROLE_ATTLIST_NONE: + if (dtd->keepProcessing && attlistDeclHandler) + handleDefault = XML_FALSE; + break; + case XML_ROLE_ELEMENT_NONE: + if (elementDeclHandler) + handleDefault = XML_FALSE; + break; + } /* end of big switch */ + + if (handleDefault && defaultHandler) + reportDefault(parser, enc, s, next); + + s = next; + tok = XmlPrologTok(enc, s, end, &next); + } + /* not reached */ +} + +static enum XML_Error PTRCALL +epilogProcessor(XML_Parser parser, + const char *s, + const char *end, + const char **nextPtr) +{ + processor = epilogProcessor; + eventPtr = s; + for (;;) { + const char *next = NULL; + int tok = XmlPrologTok(encoding, s, end, &next); + eventEndPtr = next; + switch (tok) { + /* report partial linebreak - it might be the last token */ + case -XML_TOK_PROLOG_S: + if (defaultHandler) { + eventEndPtr = next; + reportDefault(parser, encoding, s, next); + } + if (nextPtr) + *nextPtr = next; + return XML_ERROR_NONE; + case XML_TOK_NONE: + if (nextPtr) + *nextPtr = s; + return XML_ERROR_NONE; + case XML_TOK_PROLOG_S: + if (defaultHandler) + reportDefault(parser, encoding, s, next); + break; + case XML_TOK_PI: + if (!reportProcessingInstruction(parser, encoding, s, next)) + return XML_ERROR_NO_MEMORY; + break; + case XML_TOK_COMMENT: + if (!reportComment(parser, encoding, s, next)) + return XML_ERROR_NO_MEMORY; + break; + case XML_TOK_INVALID: + eventPtr = next; + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_PARTIAL: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + return XML_ERROR_UNCLOSED_TOKEN; + case XML_TOK_PARTIAL_CHAR: + if (nextPtr) { + *nextPtr = s; + return XML_ERROR_NONE; + } + return XML_ERROR_PARTIAL_CHAR; + default: + return XML_ERROR_JUNK_AFTER_DOC_ELEMENT; + } + eventPtr = s = next; + } +} + +#ifdef XML_DTD + +static enum XML_Error +processInternalParamEntity(XML_Parser parser, ENTITY *entity) +{ + const char *s, *end, *next; + int tok; + enum XML_Error result; + OPEN_INTERNAL_ENTITY openEntity; + entity->open = XML_TRUE; + openEntity.next = openInternalEntities; + openInternalEntities = &openEntity; + openEntity.entity = entity; + openEntity.internalEventPtr = NULL; + openEntity.internalEventEndPtr = NULL; + s = (char *)entity->textPtr; + end = (char *)(entity->textPtr + entity->textLen); + tok = XmlPrologTok(internalEncoding, s, end, &next); + result = doProlog(parser, internalEncoding, s, end, tok, next, 0); + entity->open = XML_FALSE; + openInternalEntities = openEntity.next; + return result; +} + +#endif /* XML_DTD */ + +static enum XML_Error PTRCALL +errorProcessor(XML_Parser parser, + const char *s, + const char *end, + const char **nextPtr) +{ + return errorCode; +} + +static enum XML_Error +storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + const char *ptr, const char *end, + STRING_POOL *pool) +{ + enum XML_Error result = appendAttributeValue(parser, enc, isCdata, ptr, + end, pool); + if (result) + return result; + if (!isCdata && poolLength(pool) && poolLastChar(pool) == 0x20) + poolChop(pool); + if (!poolAppendChar(pool, XML_T('\0'))) + return XML_ERROR_NO_MEMORY; + return XML_ERROR_NONE; +} + +static enum XML_Error +appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + const char *ptr, const char *end, + STRING_POOL *pool) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + for (;;) { + const char *next; + int tok = XmlAttributeValueTok(enc, ptr, end, &next); + switch (tok) { + case XML_TOK_NONE: + return XML_ERROR_NONE; + case XML_TOK_INVALID: + if (enc == encoding) + eventPtr = next; + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_PARTIAL: + if (enc == encoding) + eventPtr = ptr; + return XML_ERROR_INVALID_TOKEN; + case XML_TOK_CHAR_REF: + { + XML_Char buf[XML_ENCODE_MAX]; + int i; + int n = XmlCharRefNumber(enc, ptr); + if (n < 0) { + if (enc == encoding) + eventPtr = ptr; + return XML_ERROR_BAD_CHAR_REF; + } + if (!isCdata + && n == 0x20 /* space */ + && (poolLength(pool) == 0 || poolLastChar(pool) == 0x20)) + break; + n = XmlEncode(n, (ICHAR *)buf); + if (!n) { + if (enc == encoding) + eventPtr = ptr; + return XML_ERROR_BAD_CHAR_REF; + } + for (i = 0; i < n; i++) { + if (!poolAppendChar(pool, buf[i])) + return XML_ERROR_NO_MEMORY; + } + } + break; + case XML_TOK_DATA_CHARS: + if (!poolAppend(pool, enc, ptr, next)) + return XML_ERROR_NO_MEMORY; + break; + case XML_TOK_TRAILING_CR: + next = ptr + enc->minBytesPerChar; + /* fall through */ + case XML_TOK_ATTRIBUTE_VALUE_S: + case XML_TOK_DATA_NEWLINE: + if (!isCdata && (poolLength(pool) == 0 || poolLastChar(pool) == 0x20)) + break; + if (!poolAppendChar(pool, 0x20)) + return XML_ERROR_NO_MEMORY; + break; + case XML_TOK_ENTITY_REF: + { + const XML_Char *name; + ENTITY *entity; + char checkEntityDecl; + XML_Char ch = (XML_Char) XmlPredefinedEntityName(enc, + ptr + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (ch) { + if (!poolAppendChar(pool, ch)) + return XML_ERROR_NO_MEMORY; + break; + } + name = poolStoreString(&temp2Pool, enc, + ptr + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (!name) + return XML_ERROR_NO_MEMORY; + entity = (ENTITY *)lookup(&dtd->generalEntities, name, 0); + poolDiscard(&temp2Pool); + /* first, determine if a check for an existing declaration is needed; + if yes, check that the entity exists, and that it is internal, + otherwise call the default handler (if called from content) + */ + if (pool == &dtd->pool) /* are we called from prolog? */ + checkEntityDecl = +#ifdef XML_DTD + prologState.documentEntity && +#endif /* XML_DTD */ + (dtd->standalone + ? !openInternalEntities + : !dtd->hasParamEntityRefs); + else /* if (pool == &tempPool): we are called from content */ + checkEntityDecl = !dtd->hasParamEntityRefs || dtd->standalone; + if (checkEntityDecl) { + if (!entity) + return XML_ERROR_UNDEFINED_ENTITY; + else if (!entity->is_internal) + return XML_ERROR_ENTITY_DECLARED_IN_PE; + } + else if (!entity) { + /* cannot report skipped entity here - see comments on + skippedEntityHandler + if (skippedEntityHandler) + skippedEntityHandler(handlerArg, name, 0); + */ + if ((pool == &tempPool) && defaultHandler) + reportDefault(parser, enc, ptr, next); + break; + } + if (entity->open) { + if (enc == encoding) + eventPtr = ptr; + return XML_ERROR_RECURSIVE_ENTITY_REF; + } + if (entity->notation) { + if (enc == encoding) + eventPtr = ptr; + return XML_ERROR_BINARY_ENTITY_REF; + } + if (!entity->textPtr) { + if (enc == encoding) + eventPtr = ptr; + return XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF; + } + else { + enum XML_Error result; + const XML_Char *textEnd = entity->textPtr + entity->textLen; + entity->open = XML_TRUE; + result = appendAttributeValue(parser, internalEncoding, isCdata, + (char *)entity->textPtr, + (char *)textEnd, pool); + entity->open = XML_FALSE; + if (result) + return result; + } + } + break; + default: + if (enc == encoding) + eventPtr = ptr; + return XML_ERROR_UNEXPECTED_STATE; + } + ptr = next; + } + /* not reached */ +} + +static enum XML_Error +storeEntityValue(XML_Parser parser, + const ENCODING *enc, + const char *entityTextPtr, + const char *entityTextEnd) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + STRING_POOL *pool = &(dtd->entityValuePool); + enum XML_Error result = XML_ERROR_NONE; +#ifdef XML_DTD + int oldInEntityValue = prologState.inEntityValue; + prologState.inEntityValue = 1; +#endif /* XML_DTD */ + /* never return Null for the value argument in EntityDeclHandler, + since this would indicate an external entity; therefore we + have to make sure that entityValuePool.start is not null */ + if (!pool->blocks) { + if (!poolGrow(pool)) + return XML_ERROR_NO_MEMORY; + } + + for (;;) { + const char *next; + int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next); + switch (tok) { + case XML_TOK_PARAM_ENTITY_REF: +#ifdef XML_DTD + if (isParamEntity || enc != encoding) { + const XML_Char *name; + ENTITY *entity; + name = poolStoreString(&tempPool, enc, + entityTextPtr + enc->minBytesPerChar, + next - enc->minBytesPerChar); + if (!name) { + result = XML_ERROR_NO_MEMORY; + goto endEntityValue; + } + entity = (ENTITY *)lookup(&dtd->paramEntities, name, 0); + poolDiscard(&tempPool); + if (!entity) { + /* not a well-formedness error - see XML 1.0: WFC Entity Declared */ + /* cannot report skipped entity here - see comments on + skippedEntityHandler + if (skippedEntityHandler) + skippedEntityHandler(handlerArg, name, 0); + */ + dtd->keepProcessing = dtd->standalone; + goto endEntityValue; + } + if (entity->open) { + if (enc == encoding) + eventPtr = entityTextPtr; + result = XML_ERROR_RECURSIVE_ENTITY_REF; + goto endEntityValue; + } + if (entity->systemId) { + if (externalEntityRefHandler) { + dtd->paramEntityRead = XML_FALSE; + entity->open = XML_TRUE; + if (!externalEntityRefHandler(externalEntityRefHandlerArg, + 0, + entity->base, + entity->systemId, + entity->publicId)) { + entity->open = XML_FALSE; + result = XML_ERROR_EXTERNAL_ENTITY_HANDLING; + goto endEntityValue; + } + entity->open = XML_FALSE; + if (!dtd->paramEntityRead) + dtd->keepProcessing = dtd->standalone; + } + else + dtd->keepProcessing = dtd->standalone; + } + else { + entity->open = XML_TRUE; + result = storeEntityValue(parser, + internalEncoding, + (char *)entity->textPtr, + (char *)(entity->textPtr + + entity->textLen)); + entity->open = XML_FALSE; + if (result) + goto endEntityValue; + } + break; + } +#endif /* XML_DTD */ + /* in the internal subset, PE references are not legal + within markup declarations, e.g entity values in this case */ + eventPtr = entityTextPtr; + result = XML_ERROR_PARAM_ENTITY_REF; + goto endEntityValue; + case XML_TOK_NONE: + result = XML_ERROR_NONE; + goto endEntityValue; + case XML_TOK_ENTITY_REF: + case XML_TOK_DATA_CHARS: + if (!poolAppend(pool, enc, entityTextPtr, next)) { + result = XML_ERROR_NO_MEMORY; + goto endEntityValue; + } + break; + case XML_TOK_TRAILING_CR: + next = entityTextPtr + enc->minBytesPerChar; + /* fall through */ + case XML_TOK_DATA_NEWLINE: + if (pool->end == pool->ptr && !poolGrow(pool)) { + result = XML_ERROR_NO_MEMORY; + goto endEntityValue; + } + *(pool->ptr)++ = 0xA; + break; + case XML_TOK_CHAR_REF: + { + XML_Char buf[XML_ENCODE_MAX]; + int i; + int n = XmlCharRefNumber(enc, entityTextPtr); + if (n < 0) { + if (enc == encoding) + eventPtr = entityTextPtr; + result = XML_ERROR_BAD_CHAR_REF; + goto endEntityValue; + } + n = XmlEncode(n, (ICHAR *)buf); + if (!n) { + if (enc == encoding) + eventPtr = entityTextPtr; + result = XML_ERROR_BAD_CHAR_REF; + goto endEntityValue; + } + for (i = 0; i < n; i++) { + if (pool->end == pool->ptr && !poolGrow(pool)) { + result = XML_ERROR_NO_MEMORY; + goto endEntityValue; + } + *(pool->ptr)++ = buf[i]; + } + } + break; + case XML_TOK_PARTIAL: + if (enc == encoding) + eventPtr = entityTextPtr; + result = XML_ERROR_INVALID_TOKEN; + goto endEntityValue; + case XML_TOK_INVALID: + if (enc == encoding) + eventPtr = next; + result = XML_ERROR_INVALID_TOKEN; + goto endEntityValue; + default: + if (enc == encoding) + eventPtr = entityTextPtr; + result = XML_ERROR_UNEXPECTED_STATE; + goto endEntityValue; + } + entityTextPtr = next; + } +endEntityValue: +#ifdef XML_DTD + prologState.inEntityValue = oldInEntityValue; +#endif /* XML_DTD */ + return result; +} + +static void FASTCALL +normalizeLines(XML_Char *s) +{ + XML_Char *p; + for (;; s++) { + if (*s == XML_T('\0')) + return; + if (*s == 0xD) + break; + } + p = s; + do { + if (*s == 0xD) { + *p++ = 0xA; + if (*++s == 0xA) + s++; + } + else + *p++ = *s++; + } while (*s); + *p = XML_T('\0'); +} + +static int +reportProcessingInstruction(XML_Parser parser, const ENCODING *enc, + const char *start, const char *end) +{ + const XML_Char *target; + XML_Char *data; + const char *tem; + if (!processingInstructionHandler) { + if (defaultHandler) + reportDefault(parser, enc, start, end); + return 1; + } + start += enc->minBytesPerChar * 2; + tem = start + XmlNameLength(enc, start); + target = poolStoreString(&tempPool, enc, start, tem); + if (!target) + return 0; + poolFinish(&tempPool); + data = poolStoreString(&tempPool, enc, + XmlSkipS(enc, tem), + end - enc->minBytesPerChar*2); + if (!data) + return 0; + normalizeLines(data); + processingInstructionHandler(handlerArg, target, data); + poolClear(&tempPool); + return 1; +} + +static int +reportComment(XML_Parser parser, const ENCODING *enc, + const char *start, const char *end) +{ + XML_Char *data; + if (!commentHandler) { + if (defaultHandler) + reportDefault(parser, enc, start, end); + return 1; + } + data = poolStoreString(&tempPool, + enc, + start + enc->minBytesPerChar * 4, + end - enc->minBytesPerChar * 3); + if (!data) + return 0; + normalizeLines(data); + commentHandler(handlerArg, data); + poolClear(&tempPool); + return 1; +} + +static void +reportDefault(XML_Parser parser, const ENCODING *enc, + const char *s, const char *end) +{ + if (MUST_CONVERT(enc, s)) { + const char **eventPP; + const char **eventEndPP; + if (enc == encoding) { + eventPP = &eventPtr; + eventEndPP = &eventEndPtr; + } + else { + eventPP = &(openInternalEntities->internalEventPtr); + eventEndPP = &(openInternalEntities->internalEventEndPtr); + } + do { + ICHAR *dataPtr = (ICHAR *)dataBuf; + XmlConvert(enc, &s, end, &dataPtr, (ICHAR *)dataBufEnd); + *eventEndPP = s; + defaultHandler(handlerArg, dataBuf, dataPtr - (ICHAR *)dataBuf); + *eventPP = s; + } while (s != end); + } + else + defaultHandler(handlerArg, (XML_Char *)s, (XML_Char *)end - (XML_Char *)s); +} + + +static int +defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, XML_Bool isCdata, + XML_Bool isId, const XML_Char *value, XML_Parser parser) +{ + DEFAULT_ATTRIBUTE *att; + if (value || isId) { + /* The handling of default attributes gets messed up if we have + a default which duplicates a non-default. */ + int i; + for (i = 0; i < type->nDefaultAtts; i++) + if (attId == type->defaultAtts[i].id) + return 1; + if (isId && !type->idAtt && !attId->xmlns) + type->idAtt = attId; + } + if (type->nDefaultAtts == type->allocDefaultAtts) { + if (type->allocDefaultAtts == 0) { + type->allocDefaultAtts = 8; + type->defaultAtts = (DEFAULT_ATTRIBUTE *)MALLOC(type->allocDefaultAtts + * sizeof(DEFAULT_ATTRIBUTE)); + if (!type->defaultAtts) + return 0; + } + else { + DEFAULT_ATTRIBUTE *temp; + int count = type->allocDefaultAtts * 2; + temp = (DEFAULT_ATTRIBUTE *) + REALLOC(type->defaultAtts, (count * sizeof(DEFAULT_ATTRIBUTE))); + if (temp == NULL) + return 0; + type->allocDefaultAtts = count; + type->defaultAtts = temp; + } + } + att = type->defaultAtts + type->nDefaultAtts; + att->id = attId; + att->value = value; + att->isCdata = isCdata; + if (!isCdata) + attId->maybeTokenized = XML_TRUE; + type->nDefaultAtts += 1; + return 1; +} + +static int +setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *elementType) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + const XML_Char *name; + for (name = elementType->name; *name; name++) { + if (*name == XML_T(':')) { + PREFIX *prefix; + const XML_Char *s; + for (s = elementType->name; s != name; s++) { + if (!poolAppendChar(&dtd->pool, *s)) + return 0; + } + if (!poolAppendChar(&dtd->pool, XML_T('\0'))) + return 0; + prefix = (PREFIX *)lookup(&dtd->prefixes, poolStart(&dtd->pool), + sizeof(PREFIX)); + if (!prefix) + return 0; + if (prefix->name == poolStart(&dtd->pool)) + poolFinish(&dtd->pool); + else + poolDiscard(&dtd->pool); + elementType->prefix = prefix; + + } + } + return 1; +} + +static ATTRIBUTE_ID * +getAttributeId(XML_Parser parser, const ENCODING *enc, + const char *start, const char *end) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + ATTRIBUTE_ID *id; + const XML_Char *name; + if (!poolAppendChar(&dtd->pool, XML_T('\0'))) + return NULL; + name = poolStoreString(&dtd->pool, enc, start, end); + if (!name) + return NULL; + /* skip quotation mark - its storage will be re-used (like in name[-1]) */ + ++name; + id = (ATTRIBUTE_ID *)lookup(&dtd->attributeIds, name, sizeof(ATTRIBUTE_ID)); + if (!id) + return NULL; + if (id->name != name) + poolDiscard(&dtd->pool); + else { + poolFinish(&dtd->pool); + if (!ns) + ; + else if (name[0] == XML_T('x') + && name[1] == XML_T('m') + && name[2] == XML_T('l') + && name[3] == XML_T('n') + && name[4] == XML_T('s') + && (name[5] == XML_T('\0') || name[5] == XML_T(':'))) { + if (name[5] == XML_T('\0')) + id->prefix = &dtd->defaultPrefix; + else + id->prefix = (PREFIX *)lookup(&dtd->prefixes, name + 6, sizeof(PREFIX)); + id->xmlns = XML_TRUE; + } + else { + int i; + for (i = 0; name[i]; i++) { + /* attributes without prefix are *not* in the default namespace */ + if (name[i] == XML_T(':')) { + int j; + for (j = 0; j < i; j++) { + if (!poolAppendChar(&dtd->pool, name[j])) + return NULL; + } + if (!poolAppendChar(&dtd->pool, XML_T('\0'))) + return NULL; + id->prefix = (PREFIX *)lookup(&dtd->prefixes, poolStart(&dtd->pool), + sizeof(PREFIX)); + if (id->prefix->name == poolStart(&dtd->pool)) + poolFinish(&dtd->pool); + else + poolDiscard(&dtd->pool); + break; + } + } + } + } + return id; +} + +#define CONTEXT_SEP XML_T('\f') + +static const XML_Char * +getContext(XML_Parser parser) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + HASH_TABLE_ITER iter; + XML_Bool needSep = XML_FALSE; + + if (dtd->defaultPrefix.binding) { + int i; + int len; + if (!poolAppendChar(&tempPool, XML_T('='))) + return NULL; + len = dtd->defaultPrefix.binding->uriLen; + if (namespaceSeparator != XML_T('\0')) + len--; + for (i = 0; i < len; i++) + if (!poolAppendChar(&tempPool, dtd->defaultPrefix.binding->uri[i])) + return NULL; + needSep = XML_TRUE; + } + + hashTableIterInit(&iter, &(dtd->prefixes)); + for (;;) { + int i; + int len; + const XML_Char *s; + PREFIX *prefix = (PREFIX *)hashTableIterNext(&iter); + if (!prefix) + break; + if (!prefix->binding) + continue; + if (needSep && !poolAppendChar(&tempPool, CONTEXT_SEP)) + return NULL; + for (s = prefix->name; *s; s++) + if (!poolAppendChar(&tempPool, *s)) + return NULL; + if (!poolAppendChar(&tempPool, XML_T('='))) + return NULL; + len = prefix->binding->uriLen; + if (namespaceSeparator != XML_T('\0')) + len--; + for (i = 0; i < len; i++) + if (!poolAppendChar(&tempPool, prefix->binding->uri[i])) + return NULL; + needSep = XML_TRUE; + } + + + hashTableIterInit(&iter, &(dtd->generalEntities)); + for (;;) { + const XML_Char *s; + ENTITY *e = (ENTITY *)hashTableIterNext(&iter); + if (!e) + break; + if (!e->open) + continue; + if (needSep && !poolAppendChar(&tempPool, CONTEXT_SEP)) + return NULL; + for (s = e->name; *s; s++) + if (!poolAppendChar(&tempPool, *s)) + return 0; + needSep = XML_TRUE; + } + + if (!poolAppendChar(&tempPool, XML_T('\0'))) + return NULL; + return tempPool.start; +} + +static XML_Bool +setContext(XML_Parser parser, const XML_Char *context) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + const XML_Char *s = context; + + while (*context != XML_T('\0')) { + if (*s == CONTEXT_SEP || *s == XML_T('\0')) { + ENTITY *e; + if (!poolAppendChar(&tempPool, XML_T('\0'))) + return XML_FALSE; + e = (ENTITY *)lookup(&dtd->generalEntities, poolStart(&tempPool), 0); + if (e) + e->open = XML_TRUE; + if (*s != XML_T('\0')) + s++; + context = s; + poolDiscard(&tempPool); + } + else if (*s == XML_T('=')) { + PREFIX *prefix; + if (poolLength(&tempPool) == 0) + prefix = &dtd->defaultPrefix; + else { + if (!poolAppendChar(&tempPool, XML_T('\0'))) + return XML_FALSE; + prefix = (PREFIX *)lookup(&dtd->prefixes, poolStart(&tempPool), + sizeof(PREFIX)); + if (!prefix) + return XML_FALSE; + if (prefix->name == poolStart(&tempPool)) { + prefix->name = poolCopyString(&dtd->pool, prefix->name); + if (!prefix->name) + return XML_FALSE; + } + poolDiscard(&tempPool); + } + for (context = s + 1; + *context != CONTEXT_SEP && *context != XML_T('\0'); + context++) + if (!poolAppendChar(&tempPool, *context)) + return XML_FALSE; + if (!poolAppendChar(&tempPool, XML_T('\0'))) + return XML_FALSE; + if (addBinding(parser, prefix, 0, poolStart(&tempPool), + &inheritedBindings) != XML_ERROR_NONE) + return XML_FALSE; + poolDiscard(&tempPool); + if (*context != XML_T('\0')) + ++context; + s = context; + } + else { + if (!poolAppendChar(&tempPool, *s)) + return XML_FALSE; + s++; + } + } + return XML_TRUE; +} + +static void FASTCALL +normalizePublicId(XML_Char *publicId) +{ + XML_Char *p = publicId; + XML_Char *s; + for (s = publicId; *s; s++) { + switch (*s) { + case 0x20: + case 0xD: + case 0xA: + if (p != publicId && p[-1] != 0x20) + *p++ = 0x20; + break; + default: + *p++ = *s; + } + } + if (p != publicId && p[-1] == 0x20) + --p; + *p = XML_T('\0'); +} + +static DTD * +dtdCreate(const XML_Memory_Handling_Suite *ms) +{ + DTD *p = (DTD *)ms->malloc_fcn(sizeof(DTD)); + if (p == NULL) + return p; + poolInit(&(p->pool), ms); +#ifdef XML_DTD + poolInit(&(p->entityValuePool), ms); +#endif /* XML_DTD */ + hashTableInit(&(p->generalEntities), ms); + hashTableInit(&(p->elementTypes), ms); + hashTableInit(&(p->attributeIds), ms); + hashTableInit(&(p->prefixes), ms); +#ifdef XML_DTD + p->paramEntityRead = XML_FALSE; + hashTableInit(&(p->paramEntities), ms); +#endif /* XML_DTD */ + p->defaultPrefix.name = NULL; + p->defaultPrefix.binding = NULL; + + p->in_eldecl = XML_FALSE; + p->scaffIndex = NULL; + p->scaffold = NULL; + p->scaffLevel = 0; + p->scaffSize = 0; + p->scaffCount = 0; + p->contentStringLen = 0; + + p->keepProcessing = XML_TRUE; + p->hasParamEntityRefs = XML_FALSE; + p->standalone = XML_FALSE; + return p; +} + +static void +dtdReset(DTD *p, const XML_Memory_Handling_Suite *ms) +{ + HASH_TABLE_ITER iter; + hashTableIterInit(&iter, &(p->elementTypes)); + for (;;) { + ELEMENT_TYPE *e = (ELEMENT_TYPE *)hashTableIterNext(&iter); + if (!e) + break; + if (e->allocDefaultAtts != 0) + ms->free_fcn(e->defaultAtts); + } + hashTableClear(&(p->generalEntities)); +#ifdef XML_DTD + p->paramEntityRead = XML_FALSE; + hashTableClear(&(p->paramEntities)); +#endif /* XML_DTD */ + hashTableClear(&(p->elementTypes)); + hashTableClear(&(p->attributeIds)); + hashTableClear(&(p->prefixes)); + poolClear(&(p->pool)); +#ifdef XML_DTD + poolClear(&(p->entityValuePool)); +#endif /* XML_DTD */ + p->defaultPrefix.name = NULL; + p->defaultPrefix.binding = NULL; + + p->in_eldecl = XML_FALSE; + + ms->free_fcn(p->scaffIndex); + p->scaffIndex = NULL; + ms->free_fcn(p->scaffold); + p->scaffold = NULL; + + p->scaffLevel = 0; + p->scaffSize = 0; + p->scaffCount = 0; + p->contentStringLen = 0; + + p->keepProcessing = XML_TRUE; + p->hasParamEntityRefs = XML_FALSE; + p->standalone = XML_FALSE; +} + +static void +dtdDestroy(DTD *p, XML_Bool isDocEntity, const XML_Memory_Handling_Suite *ms) +{ + HASH_TABLE_ITER iter; + hashTableIterInit(&iter, &(p->elementTypes)); + for (;;) { + ELEMENT_TYPE *e = (ELEMENT_TYPE *)hashTableIterNext(&iter); + if (!e) + break; + if (e->allocDefaultAtts != 0) + ms->free_fcn(e->defaultAtts); + } + hashTableDestroy(&(p->generalEntities)); +#ifdef XML_DTD + hashTableDestroy(&(p->paramEntities)); +#endif /* XML_DTD */ + hashTableDestroy(&(p->elementTypes)); + hashTableDestroy(&(p->attributeIds)); + hashTableDestroy(&(p->prefixes)); + poolDestroy(&(p->pool)); +#ifdef XML_DTD + poolDestroy(&(p->entityValuePool)); +#endif /* XML_DTD */ + if (isDocEntity) { + ms->free_fcn(p->scaffIndex); + ms->free_fcn(p->scaffold); + } + ms->free_fcn(p); +} + +/* Do a deep copy of the DTD. Return 0 for out of memory, non-zero otherwise. + The new DTD has already been initialized. +*/ +static int +dtdCopy(DTD *newDtd, const DTD *oldDtd, const XML_Memory_Handling_Suite *ms) +{ + HASH_TABLE_ITER iter; + + /* Copy the prefix table. */ + + hashTableIterInit(&iter, &(oldDtd->prefixes)); + for (;;) { + const XML_Char *name; + const PREFIX *oldP = (PREFIX *)hashTableIterNext(&iter); + if (!oldP) + break; + name = poolCopyString(&(newDtd->pool), oldP->name); + if (!name) + return 0; + if (!lookup(&(newDtd->prefixes), name, sizeof(PREFIX))) + return 0; + } + + hashTableIterInit(&iter, &(oldDtd->attributeIds)); + + /* Copy the attribute id table. */ + + for (;;) { + ATTRIBUTE_ID *newA; + const XML_Char *name; + const ATTRIBUTE_ID *oldA = (ATTRIBUTE_ID *)hashTableIterNext(&iter); + + if (!oldA) + break; + /* Remember to allocate the scratch byte before the name. */ + if (!poolAppendChar(&(newDtd->pool), XML_T('\0'))) + return 0; + name = poolCopyString(&(newDtd->pool), oldA->name); + if (!name) + return 0; + ++name; + newA = (ATTRIBUTE_ID *)lookup(&(newDtd->attributeIds), name, + sizeof(ATTRIBUTE_ID)); + if (!newA) + return 0; + newA->maybeTokenized = oldA->maybeTokenized; + if (oldA->prefix) { + newA->xmlns = oldA->xmlns; + if (oldA->prefix == &oldDtd->defaultPrefix) + newA->prefix = &newDtd->defaultPrefix; + else + newA->prefix = (PREFIX *)lookup(&(newDtd->prefixes), + oldA->prefix->name, 0); + } + } + + /* Copy the element type table. */ + + hashTableIterInit(&iter, &(oldDtd->elementTypes)); + + for (;;) { + int i; + ELEMENT_TYPE *newE; + const XML_Char *name; + const ELEMENT_TYPE *oldE = (ELEMENT_TYPE *)hashTableIterNext(&iter); + if (!oldE) + break; + name = poolCopyString(&(newDtd->pool), oldE->name); + if (!name) + return 0; + newE = (ELEMENT_TYPE *)lookup(&(newDtd->elementTypes), name, + sizeof(ELEMENT_TYPE)); + if (!newE) + return 0; + if (oldE->nDefaultAtts) { + newE->defaultAtts = (DEFAULT_ATTRIBUTE *) + ms->malloc_fcn(oldE->nDefaultAtts * sizeof(DEFAULT_ATTRIBUTE)); + if (!newE->defaultAtts) { + ms->free_fcn(newE); + return 0; + } + } + if (oldE->idAtt) + newE->idAtt = (ATTRIBUTE_ID *) + lookup(&(newDtd->attributeIds), oldE->idAtt->name, 0); + newE->allocDefaultAtts = newE->nDefaultAtts = oldE->nDefaultAtts; + if (oldE->prefix) + newE->prefix = (PREFIX *)lookup(&(newDtd->prefixes), + oldE->prefix->name, 0); + for (i = 0; i < newE->nDefaultAtts; i++) { + newE->defaultAtts[i].id = (ATTRIBUTE_ID *) + lookup(&(newDtd->attributeIds), oldE->defaultAtts[i].id->name, 0); + newE->defaultAtts[i].isCdata = oldE->defaultAtts[i].isCdata; + if (oldE->defaultAtts[i].value) { + newE->defaultAtts[i].value + = poolCopyString(&(newDtd->pool), oldE->defaultAtts[i].value); + if (!newE->defaultAtts[i].value) + return 0; + } + else + newE->defaultAtts[i].value = NULL; + } + } + + /* Copy the entity tables. */ + if (!copyEntityTable(&(newDtd->generalEntities), + &(newDtd->pool), + &(oldDtd->generalEntities))) + return 0; + +#ifdef XML_DTD + if (!copyEntityTable(&(newDtd->paramEntities), + &(newDtd->pool), + &(oldDtd->paramEntities))) + return 0; + newDtd->paramEntityRead = oldDtd->paramEntityRead; +#endif /* XML_DTD */ + + newDtd->keepProcessing = oldDtd->keepProcessing; + newDtd->hasParamEntityRefs = oldDtd->hasParamEntityRefs; + newDtd->standalone = oldDtd->standalone; + + /* Don't want deep copying for scaffolding */ + newDtd->in_eldecl = oldDtd->in_eldecl; + newDtd->scaffold = oldDtd->scaffold; + newDtd->contentStringLen = oldDtd->contentStringLen; + newDtd->scaffSize = oldDtd->scaffSize; + newDtd->scaffLevel = oldDtd->scaffLevel; + newDtd->scaffIndex = oldDtd->scaffIndex; + + return 1; +} /* End dtdCopy */ + +static int +copyEntityTable(HASH_TABLE *newTable, + STRING_POOL *newPool, + const HASH_TABLE *oldTable) +{ + HASH_TABLE_ITER iter; + const XML_Char *cachedOldBase = NULL; + const XML_Char *cachedNewBase = NULL; + + hashTableIterInit(&iter, oldTable); + + for (;;) { + ENTITY *newE; + const XML_Char *name; + const ENTITY *oldE = (ENTITY *)hashTableIterNext(&iter); + if (!oldE) + break; + name = poolCopyString(newPool, oldE->name); + if (!name) + return 0; + newE = (ENTITY *)lookup(newTable, name, sizeof(ENTITY)); + if (!newE) + return 0; + if (oldE->systemId) { + const XML_Char *tem = poolCopyString(newPool, oldE->systemId); + if (!tem) + return 0; + newE->systemId = tem; + if (oldE->base) { + if (oldE->base == cachedOldBase) + newE->base = cachedNewBase; + else { + cachedOldBase = oldE->base; + tem = poolCopyString(newPool, cachedOldBase); + if (!tem) + return 0; + cachedNewBase = newE->base = tem; + } + } + if (oldE->publicId) { + tem = poolCopyString(newPool, oldE->publicId); + if (!tem) + return 0; + newE->publicId = tem; + } + } + else { + const XML_Char *tem = poolCopyStringN(newPool, oldE->textPtr, + oldE->textLen); + if (!tem) + return 0; + newE->textPtr = tem; + newE->textLen = oldE->textLen; + } + if (oldE->notation) { + const XML_Char *tem = poolCopyString(newPool, oldE->notation); + if (!tem) + return 0; + newE->notation = tem; + } + newE->is_param = oldE->is_param; + newE->is_internal = oldE->is_internal; + } + return 1; +} + +#define INIT_POWER 6 + +static XML_Bool FASTCALL +keyeq(KEY s1, KEY s2) +{ + for (; *s1 == *s2; s1++, s2++) + if (*s1 == 0) + return XML_TRUE; + return XML_FALSE; +} + +static unsigned long FASTCALL +hash(KEY s) +{ + unsigned long h = 0; + while (*s) + h = CHAR_HASH(h, *s++); + return h; +} + +static NAMED * +lookup(HASH_TABLE *table, KEY name, size_t createSize) +{ + size_t i; + if (table->size == 0) { + size_t tsize; + if (!createSize) + return NULL; + table->power = INIT_POWER; + /* table->size is a power of 2 */ + table->size = (size_t)1 << INIT_POWER; + tsize = table->size * sizeof(NAMED *); + table->v = (NAMED **)table->mem->malloc_fcn(tsize); + if (!table->v) + return NULL; + memset(table->v, 0, tsize); + i = hash(name) & ((unsigned long)table->size - 1); + } + else { + unsigned long h = hash(name); + unsigned long mask = (unsigned long)table->size - 1; + unsigned char step = 0; + i = h & mask; + while (table->v[i]) { + if (keyeq(name, table->v[i]->name)) + return table->v[i]; + if (!step) + step = PROBE_STEP(h, mask, table->power); + i < step ? (i += table->size - step) : (i -= step); + } + if (!createSize) + return NULL; + + /* check for overflow (table is half full) */ + if (table->used >> (table->power - 1)) { + unsigned char newPower = table->power + 1; + size_t newSize = (size_t)1 << newPower; + unsigned long newMask = (unsigned long)newSize - 1; + size_t tsize = newSize * sizeof(NAMED *); + NAMED **newV = (NAMED **)table->mem->malloc_fcn(tsize); + if (!newV) + return NULL; + memset(newV, 0, tsize); + for (i = 0; i < table->size; i++) + if (table->v[i]) { + unsigned long newHash = hash(table->v[i]->name); + size_t j = newHash & newMask; + step = 0; + while (newV[j]) { + if (!step) + step = PROBE_STEP(newHash, newMask, newPower); + j < step ? (j += newSize - step) : (j -= step); + } + newV[j] = table->v[i]; + } + table->mem->free_fcn(table->v); + table->v = newV; + table->power = newPower; + table->size = newSize; + i = h & newMask; + step = 0; + while (table->v[i]) { + if (!step) + step = PROBE_STEP(h, newMask, newPower); + i < step ? (i += newSize - step) : (i -= step); + } + } + } + table->v[i] = (NAMED *)table->mem->malloc_fcn(createSize); + if (!table->v[i]) + return NULL; + memset(table->v[i], 0, createSize); + table->v[i]->name = name; + (table->used)++; + return table->v[i]; +} + +static void FASTCALL +hashTableClear(HASH_TABLE *table) +{ + size_t i; + for (i = 0; i < table->size; i++) { + table->mem->free_fcn(table->v[i]); + table->v[i] = NULL; + } + table->used = 0; +} + +static void FASTCALL +hashTableDestroy(HASH_TABLE *table) +{ + size_t i; + for (i = 0; i < table->size; i++) + table->mem->free_fcn(table->v[i]); + table->mem->free_fcn(table->v); +} + +static void FASTCALL +hashTableInit(HASH_TABLE *p, const XML_Memory_Handling_Suite *ms) +{ + p->power = 0; + p->size = 0; + p->used = 0; + p->v = NULL; + p->mem = ms; +} + +static void FASTCALL +hashTableIterInit(HASH_TABLE_ITER *iter, const HASH_TABLE *table) +{ + iter->p = table->v; + iter->end = iter->p + table->size; +} + +static NAMED * FASTCALL +hashTableIterNext(HASH_TABLE_ITER *iter) +{ + while (iter->p != iter->end) { + NAMED *tem = *(iter->p)++; + if (tem) + return tem; + } + return NULL; +} + +static void FASTCALL +poolInit(STRING_POOL *pool, const XML_Memory_Handling_Suite *ms) +{ + pool->blocks = NULL; + pool->freeBlocks = NULL; + pool->start = NULL; + pool->ptr = NULL; + pool->end = NULL; + pool->mem = ms; +} + +static void FASTCALL +poolClear(STRING_POOL *pool) +{ + if (!pool->freeBlocks) + pool->freeBlocks = pool->blocks; + else { + BLOCK *p = pool->blocks; + while (p) { + BLOCK *tem = p->next; + p->next = pool->freeBlocks; + pool->freeBlocks = p; + p = tem; + } + } + pool->blocks = NULL; + pool->start = NULL; + pool->ptr = NULL; + pool->end = NULL; +} + +static void FASTCALL +poolDestroy(STRING_POOL *pool) +{ + BLOCK *p = pool->blocks; + while (p) { + BLOCK *tem = p->next; + pool->mem->free_fcn(p); + p = tem; + } + p = pool->freeBlocks; + while (p) { + BLOCK *tem = p->next; + pool->mem->free_fcn(p); + p = tem; + } +} + +static XML_Char * +poolAppend(STRING_POOL *pool, const ENCODING *enc, + const char *ptr, const char *end) +{ + if (!pool->ptr && !poolGrow(pool)) + return NULL; + for (;;) { + XmlConvert(enc, &ptr, end, (ICHAR **)&(pool->ptr), (ICHAR *)pool->end); + if (ptr == end) + break; + if (!poolGrow(pool)) + return NULL; + } + return pool->start; +} + +static const XML_Char * FASTCALL +poolCopyString(STRING_POOL *pool, const XML_Char *s) +{ + do { + if (!poolAppendChar(pool, *s)) + return NULL; + } while (*s++); + s = pool->start; + poolFinish(pool); + return s; +} + +static const XML_Char * +poolCopyStringN(STRING_POOL *pool, const XML_Char *s, int n) +{ + if (!pool->ptr && !poolGrow(pool)) + return NULL; + for (; n > 0; --n, s++) { + if (!poolAppendChar(pool, *s)) + return NULL; + } + s = pool->start; + poolFinish(pool); + return s; +} + +static const XML_Char * FASTCALL +poolAppendString(STRING_POOL *pool, const XML_Char *s) +{ + while (*s) { + if (!poolAppendChar(pool, *s)) + return NULL; + s++; + } + return pool->start; +} + +static XML_Char * +poolStoreString(STRING_POOL *pool, const ENCODING *enc, + const char *ptr, const char *end) +{ + if (!poolAppend(pool, enc, ptr, end)) + return NULL; + if (pool->ptr == pool->end && !poolGrow(pool)) + return NULL; + *(pool->ptr)++ = 0; + return pool->start; +} + +static XML_Bool FASTCALL +poolGrow(STRING_POOL *pool) +{ + if (pool->freeBlocks) { + if (pool->start == 0) { + pool->blocks = pool->freeBlocks; + pool->freeBlocks = pool->freeBlocks->next; + pool->blocks->next = NULL; + pool->start = pool->blocks->s; + pool->end = pool->start + pool->blocks->size; + pool->ptr = pool->start; + return XML_TRUE; + } + if (pool->end - pool->start < pool->freeBlocks->size) { + BLOCK *tem = pool->freeBlocks->next; + pool->freeBlocks->next = pool->blocks; + pool->blocks = pool->freeBlocks; + pool->freeBlocks = tem; + memcpy(pool->blocks->s, pool->start, + (pool->end - pool->start) * sizeof(XML_Char)); + pool->ptr = pool->blocks->s + (pool->ptr - pool->start); + pool->start = pool->blocks->s; + pool->end = pool->start + pool->blocks->size; + return XML_TRUE; + } + } + if (pool->blocks && pool->start == pool->blocks->s) { + int blockSize = (pool->end - pool->start)*2; + pool->blocks = (BLOCK *) + pool->mem->realloc_fcn(pool->blocks, + (offsetof(BLOCK, s) + + blockSize * sizeof(XML_Char))); + if (pool->blocks == NULL) + return XML_FALSE; + pool->blocks->size = blockSize; + pool->ptr = pool->blocks->s + (pool->ptr - pool->start); + pool->start = pool->blocks->s; + pool->end = pool->start + blockSize; + } + else { + BLOCK *tem; + int blockSize = pool->end - pool->start; + if (blockSize < INIT_BLOCK_SIZE) + blockSize = INIT_BLOCK_SIZE; + else + blockSize *= 2; + tem = (BLOCK *)pool->mem->malloc_fcn(offsetof(BLOCK, s) + + blockSize * sizeof(XML_Char)); + if (!tem) + return XML_FALSE; + tem->size = blockSize; + tem->next = pool->blocks; + pool->blocks = tem; + if (pool->ptr != pool->start) + memcpy(tem->s, pool->start, + (pool->ptr - pool->start) * sizeof(XML_Char)); + pool->ptr = tem->s + (pool->ptr - pool->start); + pool->start = tem->s; + pool->end = tem->s + blockSize; + } + return XML_TRUE; +} + +static int FASTCALL +nextScaffoldPart(XML_Parser parser) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + CONTENT_SCAFFOLD * me; + int next; + + if (!dtd->scaffIndex) { + dtd->scaffIndex = (int *)MALLOC(groupSize * sizeof(int)); + if (!dtd->scaffIndex) + return -1; + dtd->scaffIndex[0] = 0; + } + + if (dtd->scaffCount >= dtd->scaffSize) { + CONTENT_SCAFFOLD *temp; + if (dtd->scaffold) { + temp = (CONTENT_SCAFFOLD *) + REALLOC(dtd->scaffold, dtd->scaffSize * 2 * sizeof(CONTENT_SCAFFOLD)); + if (temp == NULL) + return -1; + dtd->scaffSize *= 2; + } + else { + temp = (CONTENT_SCAFFOLD *)MALLOC(INIT_SCAFFOLD_ELEMENTS + * sizeof(CONTENT_SCAFFOLD)); + if (temp == NULL) + return -1; + dtd->scaffSize = INIT_SCAFFOLD_ELEMENTS; + } + dtd->scaffold = temp; + } + next = dtd->scaffCount++; + me = &dtd->scaffold[next]; + if (dtd->scaffLevel) { + CONTENT_SCAFFOLD *parent = &dtd->scaffold[dtd->scaffIndex[dtd->scaffLevel-1]]; + if (parent->lastchild) { + dtd->scaffold[parent->lastchild].nextsib = next; + } + if (!parent->childcnt) + parent->firstchild = next; + parent->lastchild = next; + parent->childcnt++; + } + me->firstchild = me->lastchild = me->childcnt = me->nextsib = 0; + return next; +} + +static void +build_node(XML_Parser parser, + int src_node, + XML_Content *dest, + XML_Content **contpos, + XML_Char **strpos) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + dest->type = dtd->scaffold[src_node].type; + dest->quant = dtd->scaffold[src_node].quant; + if (dest->type == XML_CTYPE_NAME) { + const XML_Char *src; + dest->name = *strpos; + src = dtd->scaffold[src_node].name; + for (;;) { + *(*strpos)++ = *src; + if (!*src) + break; + src++; + } + dest->numchildren = 0; + dest->children = NULL; + } + else { + unsigned int i; + int cn; + dest->numchildren = dtd->scaffold[src_node].childcnt; + dest->children = *contpos; + *contpos += dest->numchildren; + for (i = 0, cn = dtd->scaffold[src_node].firstchild; + i < dest->numchildren; + i++, cn = dtd->scaffold[cn].nextsib) { + build_node(parser, cn, &(dest->children[i]), contpos, strpos); + } + dest->name = NULL; + } +} + +static XML_Content * +build_model (XML_Parser parser) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + XML_Content *ret; + XML_Content *cpos; + XML_Char * str; + int allocsize = (dtd->scaffCount * sizeof(XML_Content) + + (dtd->contentStringLen * sizeof(XML_Char))); + + ret = (XML_Content *)MALLOC(allocsize); + if (!ret) + return NULL; + + str = (XML_Char *) (&ret[dtd->scaffCount]); + cpos = &ret[1]; + + build_node(parser, 0, ret, &cpos, &str); + return ret; +} + +static ELEMENT_TYPE * +getElementType(XML_Parser parser, + const ENCODING *enc, + const char *ptr, + const char *end) +{ + DTD * const dtd = _dtd; /* save one level of indirection */ + const XML_Char *name = poolStoreString(&dtd->pool, enc, ptr, end); + ELEMENT_TYPE *ret; + + if (!name) + return NULL; + ret = (ELEMENT_TYPE *) lookup(&dtd->elementTypes, name, sizeof(ELEMENT_TYPE)); + if (!ret) + return NULL; + if (ret->name != name) + poolDiscard(&dtd->pool); + else { + poolFinish(&dtd->pool); + if (!setElementTypePrefix(parser, ret)) + return NULL; + } + return ret; +} diff --git a/expat/xmlrole.c b/expat/xmlrole.c new file mode 100644 index 00000000..83c0d71f --- /dev/null +++ b/expat/xmlrole.c @@ -0,0 +1,1323 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +#ifdef COMPILED_FROM_DSP +#include "winconfig.h" +#elif defined(MACOS_CLASSIC) +#include "macconfig.h" +#else +#ifdef HAVE_EXPAT_CONFIG_H +#include +#endif +#endif /* ndef COMPILED_FROM_DSP */ + +#include "internal.h" +#include "xmlrole.h" +#include "ascii.h" + +/* Doesn't check: + + that ,| are not mixed in a model group + content of literals + +*/ + +static const char KW_ANY[] = { + ASCII_A, ASCII_N, ASCII_Y, '\0' }; +static const char KW_ATTLIST[] = { + ASCII_A, ASCII_T, ASCII_T, ASCII_L, ASCII_I, ASCII_S, ASCII_T, '\0' }; +static const char KW_CDATA[] = { + ASCII_C, ASCII_D, ASCII_A, ASCII_T, ASCII_A, '\0' }; +static const char KW_DOCTYPE[] = { + ASCII_D, ASCII_O, ASCII_C, ASCII_T, ASCII_Y, ASCII_P, ASCII_E, '\0' }; +static const char KW_ELEMENT[] = { + ASCII_E, ASCII_L, ASCII_E, ASCII_M, ASCII_E, ASCII_N, ASCII_T, '\0' }; +static const char KW_EMPTY[] = { + ASCII_E, ASCII_M, ASCII_P, ASCII_T, ASCII_Y, '\0' }; +static const char KW_ENTITIES[] = { + ASCII_E, ASCII_N, ASCII_T, ASCII_I, ASCII_T, ASCII_I, ASCII_E, ASCII_S, + '\0' }; +static const char KW_ENTITY[] = { + ASCII_E, ASCII_N, ASCII_T, ASCII_I, ASCII_T, ASCII_Y, '\0' }; +static const char KW_FIXED[] = { + ASCII_F, ASCII_I, ASCII_X, ASCII_E, ASCII_D, '\0' }; +static const char KW_ID[] = { + ASCII_I, ASCII_D, '\0' }; +static const char KW_IDREF[] = { + ASCII_I, ASCII_D, ASCII_R, ASCII_E, ASCII_F, '\0' }; +static const char KW_IDREFS[] = { + ASCII_I, ASCII_D, ASCII_R, ASCII_E, ASCII_F, ASCII_S, '\0' }; +static const char KW_IGNORE[] = { + ASCII_I, ASCII_G, ASCII_N, ASCII_O, ASCII_R, ASCII_E, '\0' }; +static const char KW_IMPLIED[] = { + ASCII_I, ASCII_M, ASCII_P, ASCII_L, ASCII_I, ASCII_E, ASCII_D, '\0' }; +static const char KW_INCLUDE[] = { + ASCII_I, ASCII_N, ASCII_C, ASCII_L, ASCII_U, ASCII_D, ASCII_E, '\0' }; +static const char KW_NDATA[] = { + ASCII_N, ASCII_D, ASCII_A, ASCII_T, ASCII_A, '\0' }; +static const char KW_NMTOKEN[] = { + ASCII_N, ASCII_M, ASCII_T, ASCII_O, ASCII_K, ASCII_E, ASCII_N, '\0' }; +static const char KW_NMTOKENS[] = { + ASCII_N, ASCII_M, ASCII_T, ASCII_O, ASCII_K, ASCII_E, ASCII_N, ASCII_S, + '\0' }; +static const char KW_NOTATION[] = + { ASCII_N, ASCII_O, ASCII_T, ASCII_A, ASCII_T, ASCII_I, ASCII_O, ASCII_N, + '\0' }; +static const char KW_PCDATA[] = { + ASCII_P, ASCII_C, ASCII_D, ASCII_A, ASCII_T, ASCII_A, '\0' }; +static const char KW_PUBLIC[] = { + ASCII_P, ASCII_U, ASCII_B, ASCII_L, ASCII_I, ASCII_C, '\0' }; +static const char KW_REQUIRED[] = { + ASCII_R, ASCII_E, ASCII_Q, ASCII_U, ASCII_I, ASCII_R, ASCII_E, ASCII_D, + '\0' }; +static const char KW_SYSTEM[] = { + ASCII_S, ASCII_Y, ASCII_S, ASCII_T, ASCII_E, ASCII_M, '\0' }; + +#ifndef MIN_BYTES_PER_CHAR +#define MIN_BYTES_PER_CHAR(enc) ((enc)->minBytesPerChar) +#endif + +#ifdef XML_DTD +#define setTopLevel(state) \ + ((state)->handler = ((state)->documentEntity \ + ? internalSubset \ + : externalSubset1)) +#else /* not XML_DTD */ +#define setTopLevel(state) ((state)->handler = internalSubset) +#endif /* not XML_DTD */ + +typedef int PTRCALL PROLOG_HANDLER(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc); + +static PROLOG_HANDLER + prolog0, prolog1, prolog2, + doctype0, doctype1, doctype2, doctype3, doctype4, doctype5, + internalSubset, + entity0, entity1, entity2, entity3, entity4, entity5, entity6, + entity7, entity8, entity9, entity10, + notation0, notation1, notation2, notation3, notation4, + attlist0, attlist1, attlist2, attlist3, attlist4, attlist5, attlist6, + attlist7, attlist8, attlist9, + element0, element1, element2, element3, element4, element5, element6, + element7, +#ifdef XML_DTD + externalSubset0, externalSubset1, + condSect0, condSect1, condSect2, +#endif /* XML_DTD */ + declClose, + error; + +static int FASTCALL common(PROLOG_STATE *state, int tok); + +static int PTRCALL +prolog0(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + state->handler = prolog1; + return XML_ROLE_NONE; + case XML_TOK_XML_DECL: + state->handler = prolog1; + return XML_ROLE_XML_DECL; + case XML_TOK_PI: + state->handler = prolog1; + return XML_ROLE_PI; + case XML_TOK_COMMENT: + state->handler = prolog1; + return XML_ROLE_COMMENT; + case XML_TOK_BOM: + return XML_ROLE_NONE; + case XML_TOK_DECL_OPEN: + if (!XmlNameMatchesAscii(enc, + ptr + 2 * MIN_BYTES_PER_CHAR(enc), + end, + KW_DOCTYPE)) + break; + state->handler = doctype0; + return XML_ROLE_DOCTYPE_NONE; + case XML_TOK_INSTANCE_START: + state->handler = error; + return XML_ROLE_INSTANCE_START; + } + return common(state, tok); +} + +static int PTRCALL +prolog1(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NONE; + case XML_TOK_PI: + return XML_ROLE_PI; + case XML_TOK_COMMENT: + return XML_ROLE_COMMENT; + case XML_TOK_BOM: + return XML_ROLE_NONE; + case XML_TOK_DECL_OPEN: + if (!XmlNameMatchesAscii(enc, + ptr + 2 * MIN_BYTES_PER_CHAR(enc), + end, + KW_DOCTYPE)) + break; + state->handler = doctype0; + return XML_ROLE_DOCTYPE_NONE; + case XML_TOK_INSTANCE_START: + state->handler = error; + return XML_ROLE_INSTANCE_START; + } + return common(state, tok); +} + +static int PTRCALL +prolog2(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NONE; + case XML_TOK_PI: + return XML_ROLE_PI; + case XML_TOK_COMMENT: + return XML_ROLE_COMMENT; + case XML_TOK_INSTANCE_START: + state->handler = error; + return XML_ROLE_INSTANCE_START; + } + return common(state, tok); +} + +static int PTRCALL +doctype0(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_DOCTYPE_NONE; + case XML_TOK_NAME: + case XML_TOK_PREFIXED_NAME: + state->handler = doctype1; + return XML_ROLE_DOCTYPE_NAME; + } + return common(state, tok); +} + +static int PTRCALL +doctype1(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_DOCTYPE_NONE; + case XML_TOK_OPEN_BRACKET: + state->handler = internalSubset; + return XML_ROLE_DOCTYPE_INTERNAL_SUBSET; + case XML_TOK_DECL_CLOSE: + state->handler = prolog2; + return XML_ROLE_DOCTYPE_CLOSE; + case XML_TOK_NAME: + if (XmlNameMatchesAscii(enc, ptr, end, KW_SYSTEM)) { + state->handler = doctype3; + return XML_ROLE_DOCTYPE_NONE; + } + if (XmlNameMatchesAscii(enc, ptr, end, KW_PUBLIC)) { + state->handler = doctype2; + return XML_ROLE_DOCTYPE_NONE; + } + break; + } + return common(state, tok); +} + +static int PTRCALL +doctype2(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_DOCTYPE_NONE; + case XML_TOK_LITERAL: + state->handler = doctype3; + return XML_ROLE_DOCTYPE_PUBLIC_ID; + } + return common(state, tok); +} + +static int PTRCALL +doctype3(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_DOCTYPE_NONE; + case XML_TOK_LITERAL: + state->handler = doctype4; + return XML_ROLE_DOCTYPE_SYSTEM_ID; + } + return common(state, tok); +} + +static int PTRCALL +doctype4(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_DOCTYPE_NONE; + case XML_TOK_OPEN_BRACKET: + state->handler = internalSubset; + return XML_ROLE_DOCTYPE_INTERNAL_SUBSET; + case XML_TOK_DECL_CLOSE: + state->handler = prolog2; + return XML_ROLE_DOCTYPE_CLOSE; + } + return common(state, tok); +} + +static int PTRCALL +doctype5(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_DOCTYPE_NONE; + case XML_TOK_DECL_CLOSE: + state->handler = prolog2; + return XML_ROLE_DOCTYPE_CLOSE; + } + return common(state, tok); +} + +static int PTRCALL +internalSubset(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NONE; + case XML_TOK_DECL_OPEN: + if (XmlNameMatchesAscii(enc, + ptr + 2 * MIN_BYTES_PER_CHAR(enc), + end, + KW_ENTITY)) { + state->handler = entity0; + return XML_ROLE_ENTITY_NONE; + } + if (XmlNameMatchesAscii(enc, + ptr + 2 * MIN_BYTES_PER_CHAR(enc), + end, + KW_ATTLIST)) { + state->handler = attlist0; + return XML_ROLE_ATTLIST_NONE; + } + if (XmlNameMatchesAscii(enc, + ptr + 2 * MIN_BYTES_PER_CHAR(enc), + end, + KW_ELEMENT)) { + state->handler = element0; + return XML_ROLE_ELEMENT_NONE; + } + if (XmlNameMatchesAscii(enc, + ptr + 2 * MIN_BYTES_PER_CHAR(enc), + end, + KW_NOTATION)) { + state->handler = notation0; + return XML_ROLE_NOTATION_NONE; + } + break; + case XML_TOK_PI: + return XML_ROLE_PI; + case XML_TOK_COMMENT: + return XML_ROLE_COMMENT; + case XML_TOK_PARAM_ENTITY_REF: + return XML_ROLE_PARAM_ENTITY_REF; + case XML_TOK_CLOSE_BRACKET: + state->handler = doctype5; + return XML_ROLE_DOCTYPE_NONE; + } + return common(state, tok); +} + +#ifdef XML_DTD + +static int PTRCALL +externalSubset0(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + state->handler = externalSubset1; + if (tok == XML_TOK_XML_DECL) + return XML_ROLE_TEXT_DECL; + return externalSubset1(state, tok, ptr, end, enc); +} + +static int PTRCALL +externalSubset1(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_COND_SECT_OPEN: + state->handler = condSect0; + return XML_ROLE_NONE; + case XML_TOK_COND_SECT_CLOSE: + if (state->includeLevel == 0) + break; + state->includeLevel -= 1; + return XML_ROLE_NONE; + case XML_TOK_PROLOG_S: + return XML_ROLE_NONE; + case XML_TOK_CLOSE_BRACKET: + break; + case XML_TOK_NONE: + if (state->includeLevel) + break; + return XML_ROLE_NONE; + default: + return internalSubset(state, tok, ptr, end, enc); + } + return common(state, tok); +} + +#endif /* XML_DTD */ + +static int PTRCALL +entity0(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_PERCENT: + state->handler = entity1; + return XML_ROLE_ENTITY_NONE; + case XML_TOK_NAME: + state->handler = entity2; + return XML_ROLE_GENERAL_ENTITY_NAME; + } + return common(state, tok); +} + +static int PTRCALL +entity1(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_NAME: + state->handler = entity7; + return XML_ROLE_PARAM_ENTITY_NAME; + } + return common(state, tok); +} + +static int PTRCALL +entity2(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_NAME: + if (XmlNameMatchesAscii(enc, ptr, end, KW_SYSTEM)) { + state->handler = entity4; + return XML_ROLE_ENTITY_NONE; + } + if (XmlNameMatchesAscii(enc, ptr, end, KW_PUBLIC)) { + state->handler = entity3; + return XML_ROLE_ENTITY_NONE; + } + break; + case XML_TOK_LITERAL: + state->handler = declClose; + state->role_none = XML_ROLE_ENTITY_NONE; + return XML_ROLE_ENTITY_VALUE; + } + return common(state, tok); +} + +static int PTRCALL +entity3(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_LITERAL: + state->handler = entity4; + return XML_ROLE_ENTITY_PUBLIC_ID; + } + return common(state, tok); +} + +static int PTRCALL +entity4(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_LITERAL: + state->handler = entity5; + return XML_ROLE_ENTITY_SYSTEM_ID; + } + return common(state, tok); +} + +static int PTRCALL +entity5(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_DECL_CLOSE: + setTopLevel(state); + return XML_ROLE_ENTITY_COMPLETE; + case XML_TOK_NAME: + if (XmlNameMatchesAscii(enc, ptr, end, KW_NDATA)) { + state->handler = entity6; + return XML_ROLE_ENTITY_NONE; + } + break; + } + return common(state, tok); +} + +static int PTRCALL +entity6(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_NAME: + state->handler = declClose; + state->role_none = XML_ROLE_ENTITY_NONE; + return XML_ROLE_ENTITY_NOTATION_NAME; + } + return common(state, tok); +} + +static int PTRCALL +entity7(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_NAME: + if (XmlNameMatchesAscii(enc, ptr, end, KW_SYSTEM)) { + state->handler = entity9; + return XML_ROLE_ENTITY_NONE; + } + if (XmlNameMatchesAscii(enc, ptr, end, KW_PUBLIC)) { + state->handler = entity8; + return XML_ROLE_ENTITY_NONE; + } + break; + case XML_TOK_LITERAL: + state->handler = declClose; + state->role_none = XML_ROLE_ENTITY_NONE; + return XML_ROLE_ENTITY_VALUE; + } + return common(state, tok); +} + +static int PTRCALL +entity8(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_LITERAL: + state->handler = entity9; + return XML_ROLE_ENTITY_PUBLIC_ID; + } + return common(state, tok); +} + +static int PTRCALL +entity9(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_LITERAL: + state->handler = entity10; + return XML_ROLE_ENTITY_SYSTEM_ID; + } + return common(state, tok); +} + +static int PTRCALL +entity10(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ENTITY_NONE; + case XML_TOK_DECL_CLOSE: + setTopLevel(state); + return XML_ROLE_ENTITY_COMPLETE; + } + return common(state, tok); +} + +static int PTRCALL +notation0(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NOTATION_NONE; + case XML_TOK_NAME: + state->handler = notation1; + return XML_ROLE_NOTATION_NAME; + } + return common(state, tok); +} + +static int PTRCALL +notation1(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NOTATION_NONE; + case XML_TOK_NAME: + if (XmlNameMatchesAscii(enc, ptr, end, KW_SYSTEM)) { + state->handler = notation3; + return XML_ROLE_NOTATION_NONE; + } + if (XmlNameMatchesAscii(enc, ptr, end, KW_PUBLIC)) { + state->handler = notation2; + return XML_ROLE_NOTATION_NONE; + } + break; + } + return common(state, tok); +} + +static int PTRCALL +notation2(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NOTATION_NONE; + case XML_TOK_LITERAL: + state->handler = notation4; + return XML_ROLE_NOTATION_PUBLIC_ID; + } + return common(state, tok); +} + +static int PTRCALL +notation3(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NOTATION_NONE; + case XML_TOK_LITERAL: + state->handler = declClose; + state->role_none = XML_ROLE_NOTATION_NONE; + return XML_ROLE_NOTATION_SYSTEM_ID; + } + return common(state, tok); +} + +static int PTRCALL +notation4(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NOTATION_NONE; + case XML_TOK_LITERAL: + state->handler = declClose; + state->role_none = XML_ROLE_NOTATION_NONE; + return XML_ROLE_NOTATION_SYSTEM_ID; + case XML_TOK_DECL_CLOSE: + setTopLevel(state); + return XML_ROLE_NOTATION_NO_SYSTEM_ID; + } + return common(state, tok); +} + +static int PTRCALL +attlist0(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_NAME: + case XML_TOK_PREFIXED_NAME: + state->handler = attlist1; + return XML_ROLE_ATTLIST_ELEMENT_NAME; + } + return common(state, tok); +} + +static int PTRCALL +attlist1(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_DECL_CLOSE: + setTopLevel(state); + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_NAME: + case XML_TOK_PREFIXED_NAME: + state->handler = attlist2; + return XML_ROLE_ATTRIBUTE_NAME; + } + return common(state, tok); +} + +static int PTRCALL +attlist2(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_NAME: + { + static const char *types[] = { + KW_CDATA, + KW_ID, + KW_IDREF, + KW_IDREFS, + KW_ENTITY, + KW_ENTITIES, + KW_NMTOKEN, + KW_NMTOKENS, + }; + int i; + for (i = 0; i < (int)(sizeof(types)/sizeof(types[0])); i++) + if (XmlNameMatchesAscii(enc, ptr, end, types[i])) { + state->handler = attlist8; + return XML_ROLE_ATTRIBUTE_TYPE_CDATA + i; + } + } + if (XmlNameMatchesAscii(enc, ptr, end, KW_NOTATION)) { + state->handler = attlist5; + return XML_ROLE_ATTLIST_NONE; + } + break; + case XML_TOK_OPEN_PAREN: + state->handler = attlist3; + return XML_ROLE_ATTLIST_NONE; + } + return common(state, tok); +} + +static int PTRCALL +attlist3(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_NMTOKEN: + case XML_TOK_NAME: + case XML_TOK_PREFIXED_NAME: + state->handler = attlist4; + return XML_ROLE_ATTRIBUTE_ENUM_VALUE; + } + return common(state, tok); +} + +static int PTRCALL +attlist4(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_CLOSE_PAREN: + state->handler = attlist8; + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_OR: + state->handler = attlist3; + return XML_ROLE_ATTLIST_NONE; + } + return common(state, tok); +} + +static int PTRCALL +attlist5(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_OPEN_PAREN: + state->handler = attlist6; + return XML_ROLE_ATTLIST_NONE; + } + return common(state, tok); +} + +static int PTRCALL +attlist6(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_NAME: + state->handler = attlist7; + return XML_ROLE_ATTRIBUTE_NOTATION_VALUE; + } + return common(state, tok); +} + +static int PTRCALL +attlist7(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_CLOSE_PAREN: + state->handler = attlist8; + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_OR: + state->handler = attlist6; + return XML_ROLE_ATTLIST_NONE; + } + return common(state, tok); +} + +/* default value */ +static int PTRCALL +attlist8(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_POUND_NAME: + if (XmlNameMatchesAscii(enc, + ptr + MIN_BYTES_PER_CHAR(enc), + end, + KW_IMPLIED)) { + state->handler = attlist1; + return XML_ROLE_IMPLIED_ATTRIBUTE_VALUE; + } + if (XmlNameMatchesAscii(enc, + ptr + MIN_BYTES_PER_CHAR(enc), + end, + KW_REQUIRED)) { + state->handler = attlist1; + return XML_ROLE_REQUIRED_ATTRIBUTE_VALUE; + } + if (XmlNameMatchesAscii(enc, + ptr + MIN_BYTES_PER_CHAR(enc), + end, + KW_FIXED)) { + state->handler = attlist9; + return XML_ROLE_ATTLIST_NONE; + } + break; + case XML_TOK_LITERAL: + state->handler = attlist1; + return XML_ROLE_DEFAULT_ATTRIBUTE_VALUE; + } + return common(state, tok); +} + +static int PTRCALL +attlist9(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ATTLIST_NONE; + case XML_TOK_LITERAL: + state->handler = attlist1; + return XML_ROLE_FIXED_ATTRIBUTE_VALUE; + } + return common(state, tok); +} + +static int PTRCALL +element0(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ELEMENT_NONE; + case XML_TOK_NAME: + case XML_TOK_PREFIXED_NAME: + state->handler = element1; + return XML_ROLE_ELEMENT_NAME; + } + return common(state, tok); +} + +static int PTRCALL +element1(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ELEMENT_NONE; + case XML_TOK_NAME: + if (XmlNameMatchesAscii(enc, ptr, end, KW_EMPTY)) { + state->handler = declClose; + state->role_none = XML_ROLE_ELEMENT_NONE; + return XML_ROLE_CONTENT_EMPTY; + } + if (XmlNameMatchesAscii(enc, ptr, end, KW_ANY)) { + state->handler = declClose; + state->role_none = XML_ROLE_ELEMENT_NONE; + return XML_ROLE_CONTENT_ANY; + } + break; + case XML_TOK_OPEN_PAREN: + state->handler = element2; + state->level = 1; + return XML_ROLE_GROUP_OPEN; + } + return common(state, tok); +} + +static int PTRCALL +element2(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ELEMENT_NONE; + case XML_TOK_POUND_NAME: + if (XmlNameMatchesAscii(enc, + ptr + MIN_BYTES_PER_CHAR(enc), + end, + KW_PCDATA)) { + state->handler = element3; + return XML_ROLE_CONTENT_PCDATA; + } + break; + case XML_TOK_OPEN_PAREN: + state->level = 2; + state->handler = element6; + return XML_ROLE_GROUP_OPEN; + case XML_TOK_NAME: + case XML_TOK_PREFIXED_NAME: + state->handler = element7; + return XML_ROLE_CONTENT_ELEMENT; + case XML_TOK_NAME_QUESTION: + state->handler = element7; + return XML_ROLE_CONTENT_ELEMENT_OPT; + case XML_TOK_NAME_ASTERISK: + state->handler = element7; + return XML_ROLE_CONTENT_ELEMENT_REP; + case XML_TOK_NAME_PLUS: + state->handler = element7; + return XML_ROLE_CONTENT_ELEMENT_PLUS; + } + return common(state, tok); +} + +static int PTRCALL +element3(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ELEMENT_NONE; + case XML_TOK_CLOSE_PAREN: + state->handler = declClose; + state->role_none = XML_ROLE_ELEMENT_NONE; + return XML_ROLE_GROUP_CLOSE; + case XML_TOK_CLOSE_PAREN_ASTERISK: + state->handler = declClose; + state->role_none = XML_ROLE_ELEMENT_NONE; + return XML_ROLE_GROUP_CLOSE_REP; + case XML_TOK_OR: + state->handler = element4; + return XML_ROLE_ELEMENT_NONE; + } + return common(state, tok); +} + +static int PTRCALL +element4(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ELEMENT_NONE; + case XML_TOK_NAME: + case XML_TOK_PREFIXED_NAME: + state->handler = element5; + return XML_ROLE_CONTENT_ELEMENT; + } + return common(state, tok); +} + +static int PTRCALL +element5(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ELEMENT_NONE; + case XML_TOK_CLOSE_PAREN_ASTERISK: + state->handler = declClose; + state->role_none = XML_ROLE_ELEMENT_NONE; + return XML_ROLE_GROUP_CLOSE_REP; + case XML_TOK_OR: + state->handler = element4; + return XML_ROLE_ELEMENT_NONE; + } + return common(state, tok); +} + +static int PTRCALL +element6(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ELEMENT_NONE; + case XML_TOK_OPEN_PAREN: + state->level += 1; + return XML_ROLE_GROUP_OPEN; + case XML_TOK_NAME: + case XML_TOK_PREFIXED_NAME: + state->handler = element7; + return XML_ROLE_CONTENT_ELEMENT; + case XML_TOK_NAME_QUESTION: + state->handler = element7; + return XML_ROLE_CONTENT_ELEMENT_OPT; + case XML_TOK_NAME_ASTERISK: + state->handler = element7; + return XML_ROLE_CONTENT_ELEMENT_REP; + case XML_TOK_NAME_PLUS: + state->handler = element7; + return XML_ROLE_CONTENT_ELEMENT_PLUS; + } + return common(state, tok); +} + +static int PTRCALL +element7(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_ELEMENT_NONE; + case XML_TOK_CLOSE_PAREN: + state->level -= 1; + if (state->level == 0) { + state->handler = declClose; + state->role_none = XML_ROLE_ELEMENT_NONE; + } + return XML_ROLE_GROUP_CLOSE; + case XML_TOK_CLOSE_PAREN_ASTERISK: + state->level -= 1; + if (state->level == 0) { + state->handler = declClose; + state->role_none = XML_ROLE_ELEMENT_NONE; + } + return XML_ROLE_GROUP_CLOSE_REP; + case XML_TOK_CLOSE_PAREN_QUESTION: + state->level -= 1; + if (state->level == 0) { + state->handler = declClose; + state->role_none = XML_ROLE_ELEMENT_NONE; + } + return XML_ROLE_GROUP_CLOSE_OPT; + case XML_TOK_CLOSE_PAREN_PLUS: + state->level -= 1; + if (state->level == 0) { + state->handler = declClose; + state->role_none = XML_ROLE_ELEMENT_NONE; + } + return XML_ROLE_GROUP_CLOSE_PLUS; + case XML_TOK_COMMA: + state->handler = element6; + return XML_ROLE_GROUP_SEQUENCE; + case XML_TOK_OR: + state->handler = element6; + return XML_ROLE_GROUP_CHOICE; + } + return common(state, tok); +} + +#ifdef XML_DTD + +static int PTRCALL +condSect0(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NONE; + case XML_TOK_NAME: + if (XmlNameMatchesAscii(enc, ptr, end, KW_INCLUDE)) { + state->handler = condSect1; + return XML_ROLE_NONE; + } + if (XmlNameMatchesAscii(enc, ptr, end, KW_IGNORE)) { + state->handler = condSect2; + return XML_ROLE_NONE; + } + break; + } + return common(state, tok); +} + +static int PTRCALL +condSect1(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NONE; + case XML_TOK_OPEN_BRACKET: + state->handler = externalSubset1; + state->includeLevel += 1; + return XML_ROLE_NONE; + } + return common(state, tok); +} + +static int PTRCALL +condSect2(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return XML_ROLE_NONE; + case XML_TOK_OPEN_BRACKET: + state->handler = externalSubset1; + return XML_ROLE_IGNORE_SECT; + } + return common(state, tok); +} + +#endif /* XML_DTD */ + +static int PTRCALL +declClose(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + switch (tok) { + case XML_TOK_PROLOG_S: + return state->role_none; + case XML_TOK_DECL_CLOSE: + setTopLevel(state); + return state->role_none; + } + return common(state, tok); +} + +static int PTRCALL +error(PROLOG_STATE *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc) +{ + return XML_ROLE_NONE; +} + +static int FASTCALL +common(PROLOG_STATE *state, int tok) +{ +#ifdef XML_DTD + if (!state->documentEntity && tok == XML_TOK_PARAM_ENTITY_REF) + return XML_ROLE_INNER_PARAM_ENTITY_REF; +#endif + state->handler = error; + return XML_ROLE_ERROR; +} + +void +XmlPrologStateInit(PROLOG_STATE *state) +{ + state->handler = prolog0; +#ifdef XML_DTD + state->documentEntity = 1; + state->includeLevel = 0; + state->inEntityValue = 0; +#endif /* XML_DTD */ +} + +#ifdef XML_DTD + +void +XmlPrologStateInitExternalEntity(PROLOG_STATE *state) +{ + state->handler = externalSubset0; + state->documentEntity = 0; + state->includeLevel = 0; +} + +#endif /* XML_DTD */ diff --git a/expat/xmlrole.h b/expat/xmlrole.h new file mode 100644 index 00000000..4dd9f06f --- /dev/null +++ b/expat/xmlrole.h @@ -0,0 +1,114 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +#ifndef XmlRole_INCLUDED +#define XmlRole_INCLUDED 1 + +#ifdef __VMS +/* 0 1 2 3 0 1 2 3 + 1234567890123456789012345678901 1234567890123456789012345678901 */ +#define XmlPrologStateInitExternalEntity XmlPrologStateInitExternalEnt +#endif + +#include "xmltok.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + XML_ROLE_ERROR = -1, + XML_ROLE_NONE = 0, + XML_ROLE_XML_DECL, + XML_ROLE_INSTANCE_START, + XML_ROLE_DOCTYPE_NONE, + XML_ROLE_DOCTYPE_NAME, + XML_ROLE_DOCTYPE_SYSTEM_ID, + XML_ROLE_DOCTYPE_PUBLIC_ID, + XML_ROLE_DOCTYPE_INTERNAL_SUBSET, + XML_ROLE_DOCTYPE_CLOSE, + XML_ROLE_GENERAL_ENTITY_NAME, + XML_ROLE_PARAM_ENTITY_NAME, + XML_ROLE_ENTITY_NONE, + XML_ROLE_ENTITY_VALUE, + XML_ROLE_ENTITY_SYSTEM_ID, + XML_ROLE_ENTITY_PUBLIC_ID, + XML_ROLE_ENTITY_COMPLETE, + XML_ROLE_ENTITY_NOTATION_NAME, + XML_ROLE_NOTATION_NONE, + XML_ROLE_NOTATION_NAME, + XML_ROLE_NOTATION_SYSTEM_ID, + XML_ROLE_NOTATION_NO_SYSTEM_ID, + XML_ROLE_NOTATION_PUBLIC_ID, + XML_ROLE_ATTRIBUTE_NAME, + XML_ROLE_ATTRIBUTE_TYPE_CDATA, + XML_ROLE_ATTRIBUTE_TYPE_ID, + XML_ROLE_ATTRIBUTE_TYPE_IDREF, + XML_ROLE_ATTRIBUTE_TYPE_IDREFS, + XML_ROLE_ATTRIBUTE_TYPE_ENTITY, + XML_ROLE_ATTRIBUTE_TYPE_ENTITIES, + XML_ROLE_ATTRIBUTE_TYPE_NMTOKEN, + XML_ROLE_ATTRIBUTE_TYPE_NMTOKENS, + XML_ROLE_ATTRIBUTE_ENUM_VALUE, + XML_ROLE_ATTRIBUTE_NOTATION_VALUE, + XML_ROLE_ATTLIST_NONE, + XML_ROLE_ATTLIST_ELEMENT_NAME, + XML_ROLE_IMPLIED_ATTRIBUTE_VALUE, + XML_ROLE_REQUIRED_ATTRIBUTE_VALUE, + XML_ROLE_DEFAULT_ATTRIBUTE_VALUE, + XML_ROLE_FIXED_ATTRIBUTE_VALUE, + XML_ROLE_ELEMENT_NONE, + XML_ROLE_ELEMENT_NAME, + XML_ROLE_CONTENT_ANY, + XML_ROLE_CONTENT_EMPTY, + XML_ROLE_CONTENT_PCDATA, + XML_ROLE_GROUP_OPEN, + XML_ROLE_GROUP_CLOSE, + XML_ROLE_GROUP_CLOSE_REP, + XML_ROLE_GROUP_CLOSE_OPT, + XML_ROLE_GROUP_CLOSE_PLUS, + XML_ROLE_GROUP_CHOICE, + XML_ROLE_GROUP_SEQUENCE, + XML_ROLE_CONTENT_ELEMENT, + XML_ROLE_CONTENT_ELEMENT_REP, + XML_ROLE_CONTENT_ELEMENT_OPT, + XML_ROLE_CONTENT_ELEMENT_PLUS, + XML_ROLE_PI, + XML_ROLE_COMMENT, +#ifdef XML_DTD + XML_ROLE_TEXT_DECL, + XML_ROLE_IGNORE_SECT, + XML_ROLE_INNER_PARAM_ENTITY_REF, +#endif /* XML_DTD */ + XML_ROLE_PARAM_ENTITY_REF +}; + +typedef struct prolog_state { + int (PTRCALL *handler) (struct prolog_state *state, + int tok, + const char *ptr, + const char *end, + const ENCODING *enc); + unsigned level; + int role_none; +#ifdef XML_DTD + unsigned includeLevel; + int documentEntity; + int inEntityValue; +#endif /* XML_DTD */ +} PROLOG_STATE; + +void XmlPrologStateInit(PROLOG_STATE *); +#ifdef XML_DTD +void XmlPrologStateInitExternalEntity(PROLOG_STATE *); +#endif /* XML_DTD */ + +#define XmlTokenRole(state, tok, ptr, end, enc) \ + (((state)->handler)(state, tok, ptr, end, enc)) + +#ifdef __cplusplus +} +#endif + +#endif /* not XmlRole_INCLUDED */ diff --git a/expat/xmltok.c b/expat/xmltok.c new file mode 100644 index 00000000..962df0e9 --- /dev/null +++ b/expat/xmltok.c @@ -0,0 +1,1634 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +#ifdef COMPILED_FROM_DSP +#include "winconfig.h" +#elif defined(MACOS_CLASSIC) +#include "macconfig.h" +#else +#ifdef HAVE_EXPAT_CONFIG_H +#include +#endif +#endif /* ndef COMPILED_FROM_DSP */ + +#include "internal.h" +#include "xmltok.h" +#include "nametab.h" + +#ifdef XML_DTD +#define IGNORE_SECTION_TOK_VTABLE , PREFIX(ignoreSectionTok) +#else +#define IGNORE_SECTION_TOK_VTABLE /* as nothing */ +#endif + +#define VTABLE1 \ + { PREFIX(prologTok), PREFIX(contentTok), \ + PREFIX(cdataSectionTok) IGNORE_SECTION_TOK_VTABLE }, \ + { PREFIX(attributeValueTok), PREFIX(entityValueTok) }, \ + PREFIX(sameName), \ + PREFIX(nameMatchesAscii), \ + PREFIX(nameLength), \ + PREFIX(skipS), \ + PREFIX(getAtts), \ + PREFIX(charRefNumber), \ + PREFIX(predefinedEntityName), \ + PREFIX(updatePosition), \ + PREFIX(isPublicId) + +#define VTABLE VTABLE1, PREFIX(toUtf8), PREFIX(toUtf16) + +#define UCS2_GET_NAMING(pages, hi, lo) \ + (namingBitmap[(pages[hi] << 3) + ((lo) >> 5)] & (1 << ((lo) & 0x1F))) + +/* A 2 byte UTF-8 representation splits the characters 11 bits between + the bottom 5 and 6 bits of the bytes. We need 8 bits to index into + pages, 3 bits to add to that index and 5 bits to generate the mask. +*/ +#define UTF8_GET_NAMING2(pages, byte) \ + (namingBitmap[((pages)[(((byte)[0]) >> 2) & 7] << 3) \ + + ((((byte)[0]) & 3) << 1) \ + + ((((byte)[1]) >> 5) & 1)] \ + & (1 << (((byte)[1]) & 0x1F))) + +/* A 3 byte UTF-8 representation splits the characters 16 bits between + the bottom 4, 6 and 6 bits of the bytes. We need 8 bits to index + into pages, 3 bits to add to that index and 5 bits to generate the + mask. +*/ +#define UTF8_GET_NAMING3(pages, byte) \ + (namingBitmap[((pages)[((((byte)[0]) & 0xF) << 4) \ + + ((((byte)[1]) >> 2) & 0xF)] \ + << 3) \ + + ((((byte)[1]) & 3) << 1) \ + + ((((byte)[2]) >> 5) & 1)] \ + & (1 << (((byte)[2]) & 0x1F))) + +#define UTF8_GET_NAMING(pages, p, n) \ + ((n) == 2 \ + ? UTF8_GET_NAMING2(pages, (const unsigned char *)(p)) \ + : ((n) == 3 \ + ? UTF8_GET_NAMING3(pages, (const unsigned char *)(p)) \ + : 0)) + +/* Detection of invalid UTF-8 sequences is based on Table 3.1B + of Unicode 3.2: http://www.unicode.org/unicode/reports/tr28/ + with the additional restriction of not allowing the Unicode + code points 0xFFFF and 0xFFFE (sequences EF,BF,BF and EF,BF,BE). + Implementation details: + (A & 0x80) == 0 means A < 0x80 + and + (A & 0xC0) == 0xC0 means A > 0xBF +*/ + +#define UTF8_INVALID2(p) \ + ((*p) < 0xC2 || ((p)[1] & 0x80) == 0 || ((p)[1] & 0xC0) == 0xC0) + +#define UTF8_INVALID3(p) \ + (((p)[2] & 0x80) == 0 \ + || \ + ((*p) == 0xEF && (p)[1] == 0xBF \ + ? \ + (p)[2] > 0xBD \ + : \ + ((p)[2] & 0xC0) == 0xC0) \ + || \ + ((*p) == 0xE0 \ + ? \ + (p)[1] < 0xA0 || ((p)[1] & 0xC0) == 0xC0 \ + : \ + ((p)[1] & 0x80) == 0 \ + || \ + ((*p) == 0xED ? (p)[1] > 0x9F : ((p)[1] & 0xC0) == 0xC0))) + +#define UTF8_INVALID4(p) \ + (((p)[3] & 0x80) == 0 || ((p)[3] & 0xC0) == 0xC0 \ + || \ + ((p)[2] & 0x80) == 0 || ((p)[2] & 0xC0) == 0xC0 \ + || \ + ((*p) == 0xF0 \ + ? \ + (p)[1] < 0x90 || ((p)[1] & 0xC0) == 0xC0 \ + : \ + ((p)[1] & 0x80) == 0 \ + || \ + ((*p) == 0xF4 ? (p)[1] > 0x8F : ((p)[1] & 0xC0) == 0xC0))) + +static int PTRFASTCALL +isNever(const ENCODING *enc, const char *p) +{ + return 0; +} + +static int PTRFASTCALL +utf8_isName2(const ENCODING *enc, const char *p) +{ + return UTF8_GET_NAMING2(namePages, (const unsigned char *)p); +} + +static int PTRFASTCALL +utf8_isName3(const ENCODING *enc, const char *p) +{ + return UTF8_GET_NAMING3(namePages, (const unsigned char *)p); +} + +#define utf8_isName4 isNever + +static int PTRFASTCALL +utf8_isNmstrt2(const ENCODING *enc, const char *p) +{ + return UTF8_GET_NAMING2(nmstrtPages, (const unsigned char *)p); +} + +static int PTRFASTCALL +utf8_isNmstrt3(const ENCODING *enc, const char *p) +{ + return UTF8_GET_NAMING3(nmstrtPages, (const unsigned char *)p); +} + +#define utf8_isNmstrt4 isNever + +static int PTRFASTCALL +utf8_isInvalid2(const ENCODING *enc, const char *p) +{ + return UTF8_INVALID2((const unsigned char *)p); +} + +static int PTRFASTCALL +utf8_isInvalid3(const ENCODING *enc, const char *p) +{ + return UTF8_INVALID3((const unsigned char *)p); +} + +static int PTRFASTCALL +utf8_isInvalid4(const ENCODING *enc, const char *p) +{ + return UTF8_INVALID4((const unsigned char *)p); +} + +struct normal_encoding { + ENCODING enc; + unsigned char type[256]; +#ifdef XML_MIN_SIZE + int (PTRFASTCALL *byteType)(const ENCODING *, const char *); + int (PTRFASTCALL *isNameMin)(const ENCODING *, const char *); + int (PTRFASTCALL *isNmstrtMin)(const ENCODING *, const char *); + int (PTRFASTCALL *byteToAscii)(const ENCODING *, const char *); + int (PTRCALL *charMatches)(const ENCODING *, const char *, int); +#endif /* XML_MIN_SIZE */ + int (PTRFASTCALL *isName2)(const ENCODING *, const char *); + int (PTRFASTCALL *isName3)(const ENCODING *, const char *); + int (PTRFASTCALL *isName4)(const ENCODING *, const char *); + int (PTRFASTCALL *isNmstrt2)(const ENCODING *, const char *); + int (PTRFASTCALL *isNmstrt3)(const ENCODING *, const char *); + int (PTRFASTCALL *isNmstrt4)(const ENCODING *, const char *); + int (PTRFASTCALL *isInvalid2)(const ENCODING *, const char *); + int (PTRFASTCALL *isInvalid3)(const ENCODING *, const char *); + int (PTRFASTCALL *isInvalid4)(const ENCODING *, const char *); +}; + +#define AS_NORMAL_ENCODING(enc) ((const struct normal_encoding *) (enc)) + +#ifdef XML_MIN_SIZE + +#define STANDARD_VTABLE(E) \ + E ## byteType, \ + E ## isNameMin, \ + E ## isNmstrtMin, \ + E ## byteToAscii, \ + E ## charMatches, + +#else + +#define STANDARD_VTABLE(E) /* as nothing */ + +#endif + +#define NORMAL_VTABLE(E) \ + E ## isName2, \ + E ## isName3, \ + E ## isName4, \ + E ## isNmstrt2, \ + E ## isNmstrt3, \ + E ## isNmstrt4, \ + E ## isInvalid2, \ + E ## isInvalid3, \ + E ## isInvalid4 + +static int FASTCALL checkCharRefNumber(int); + +#include "xmltok_impl.h" +#include "ascii.h" + +#ifdef XML_MIN_SIZE +#define sb_isNameMin isNever +#define sb_isNmstrtMin isNever +#endif + +#ifdef XML_MIN_SIZE +#define MINBPC(enc) ((enc)->minBytesPerChar) +#else +/* minimum bytes per character */ +#define MINBPC(enc) 1 +#endif + +#define SB_BYTE_TYPE(enc, p) \ + (((struct normal_encoding *)(enc))->type[(unsigned char)*(p)]) + +#ifdef XML_MIN_SIZE +static int PTRFASTCALL +sb_byteType(const ENCODING *enc, const char *p) +{ + return SB_BYTE_TYPE(enc, p); +} +#define BYTE_TYPE(enc, p) \ + (AS_NORMAL_ENCODING(enc)->byteType(enc, p)) +#else +#define BYTE_TYPE(enc, p) SB_BYTE_TYPE(enc, p) +#endif + +#ifdef XML_MIN_SIZE +#define BYTE_TO_ASCII(enc, p) \ + (AS_NORMAL_ENCODING(enc)->byteToAscii(enc, p)) +static int PTRFASTCALL +sb_byteToAscii(const ENCODING *enc, const char *p) +{ + return *p; +} +#else +#define BYTE_TO_ASCII(enc, p) (*(p)) +#endif + +#define IS_NAME_CHAR(enc, p, n) \ + (AS_NORMAL_ENCODING(enc)->isName ## n(enc, p)) +#define IS_NMSTRT_CHAR(enc, p, n) \ + (AS_NORMAL_ENCODING(enc)->isNmstrt ## n(enc, p)) +#define IS_INVALID_CHAR(enc, p, n) \ + (AS_NORMAL_ENCODING(enc)->isInvalid ## n(enc, p)) + +#ifdef XML_MIN_SIZE +#define IS_NAME_CHAR_MINBPC(enc, p) \ + (AS_NORMAL_ENCODING(enc)->isNameMin(enc, p)) +#define IS_NMSTRT_CHAR_MINBPC(enc, p) \ + (AS_NORMAL_ENCODING(enc)->isNmstrtMin(enc, p)) +#else +#define IS_NAME_CHAR_MINBPC(enc, p) (0) +#define IS_NMSTRT_CHAR_MINBPC(enc, p) (0) +#endif + +#ifdef XML_MIN_SIZE +#define CHAR_MATCHES(enc, p, c) \ + (AS_NORMAL_ENCODING(enc)->charMatches(enc, p, c)) +static int PTRCALL +sb_charMatches(const ENCODING *enc, const char *p, int c) +{ + return *p == c; +} +#else +/* c is an ASCII character */ +#define CHAR_MATCHES(enc, p, c) (*(p) == c) +#endif + +#define PREFIX(ident) normal_ ## ident +#include "xmltok_impl.c" + +#undef MINBPC +#undef BYTE_TYPE +#undef BYTE_TO_ASCII +#undef CHAR_MATCHES +#undef IS_NAME_CHAR +#undef IS_NAME_CHAR_MINBPC +#undef IS_NMSTRT_CHAR +#undef IS_NMSTRT_CHAR_MINBPC +#undef IS_INVALID_CHAR + +enum { /* UTF8_cvalN is value of masked first byte of N byte sequence */ + UTF8_cval1 = 0x00, + UTF8_cval2 = 0xc0, + UTF8_cval3 = 0xe0, + UTF8_cval4 = 0xf0 +}; + +static void PTRCALL +utf8_toUtf8(const ENCODING *enc, + const char **fromP, const char *fromLim, + char **toP, const char *toLim) +{ + char *to; + const char *from; + if (fromLim - *fromP > toLim - *toP) { + /* Avoid copying partial characters. */ + for (fromLim = *fromP + (toLim - *toP); fromLim > *fromP; fromLim--) + if (((unsigned char)fromLim[-1] & 0xc0) != 0x80) + break; + } + for (to = *toP, from = *fromP; from != fromLim; from++, to++) + *to = *from; + *fromP = from; + *toP = to; +} + +static void PTRCALL +utf8_toUtf16(const ENCODING *enc, + const char **fromP, const char *fromLim, + unsigned short **toP, const unsigned short *toLim) +{ + unsigned short *to = *toP; + const char *from = *fromP; + while (from != fromLim && to != toLim) { + switch (((struct normal_encoding *)enc)->type[(unsigned char)*from]) { + case BT_LEAD2: + *to++ = (unsigned short)(((from[0] & 0x1f) << 6) | (from[1] & 0x3f)); + from += 2; + break; + case BT_LEAD3: + *to++ = (unsigned short)(((from[0] & 0xf) << 12) + | ((from[1] & 0x3f) << 6) | (from[2] & 0x3f)); + from += 3; + break; + case BT_LEAD4: + { + unsigned long n; + if (to + 1 == toLim) + goto after; + n = ((from[0] & 0x7) << 18) | ((from[1] & 0x3f) << 12) + | ((from[2] & 0x3f) << 6) | (from[3] & 0x3f); + n -= 0x10000; + to[0] = (unsigned short)((n >> 10) | 0xD800); + to[1] = (unsigned short)((n & 0x3FF) | 0xDC00); + to += 2; + from += 4; + } + break; + default: + *to++ = *from++; + break; + } + } +after: + *fromP = from; + *toP = to; +} + +#ifdef XML_NS +static const struct normal_encoding utf8_encoding_ns = { + { VTABLE1, utf8_toUtf8, utf8_toUtf16, 1, 1, 0 }, + { +#include "asciitab.h" +#include "utf8tab.h" + }, + STANDARD_VTABLE(sb_) NORMAL_VTABLE(utf8_) +}; +#endif + +static const struct normal_encoding utf8_encoding = { + { VTABLE1, utf8_toUtf8, utf8_toUtf16, 1, 1, 0 }, + { +#define BT_COLON BT_NMSTRT +#include "asciitab.h" +#undef BT_COLON +#include "utf8tab.h" + }, + STANDARD_VTABLE(sb_) NORMAL_VTABLE(utf8_) +}; + +#ifdef XML_NS + +static const struct normal_encoding internal_utf8_encoding_ns = { + { VTABLE1, utf8_toUtf8, utf8_toUtf16, 1, 1, 0 }, + { +#include "iasciitab.h" +#include "utf8tab.h" + }, + STANDARD_VTABLE(sb_) NORMAL_VTABLE(utf8_) +}; + +#endif + +static const struct normal_encoding internal_utf8_encoding = { + { VTABLE1, utf8_toUtf8, utf8_toUtf16, 1, 1, 0 }, + { +#define BT_COLON BT_NMSTRT +#include "iasciitab.h" +#undef BT_COLON +#include "utf8tab.h" + }, + STANDARD_VTABLE(sb_) NORMAL_VTABLE(utf8_) +}; + +static void PTRCALL +latin1_toUtf8(const ENCODING *enc, + const char **fromP, const char *fromLim, + char **toP, const char *toLim) +{ + for (;;) { + unsigned char c; + if (*fromP == fromLim) + break; + c = (unsigned char)**fromP; + if (c & 0x80) { + if (toLim - *toP < 2) + break; + *(*toP)++ = (char)((c >> 6) | UTF8_cval2); + *(*toP)++ = (char)((c & 0x3f) | 0x80); + (*fromP)++; + } + else { + if (*toP == toLim) + break; + *(*toP)++ = *(*fromP)++; + } + } +} + +static void PTRCALL +latin1_toUtf16(const ENCODING *enc, + const char **fromP, const char *fromLim, + unsigned short **toP, const unsigned short *toLim) +{ + while (*fromP != fromLim && *toP != toLim) + *(*toP)++ = (unsigned char)*(*fromP)++; +} + +#ifdef XML_NS + +static const struct normal_encoding latin1_encoding_ns = { + { VTABLE1, latin1_toUtf8, latin1_toUtf16, 1, 0, 0 }, + { +#include "asciitab.h" +#include "latin1tab.h" + }, + STANDARD_VTABLE(sb_) +}; + +#endif + +static const struct normal_encoding latin1_encoding = { + { VTABLE1, latin1_toUtf8, latin1_toUtf16, 1, 0, 0 }, + { +#define BT_COLON BT_NMSTRT +#include "asciitab.h" +#undef BT_COLON +#include "latin1tab.h" + }, + STANDARD_VTABLE(sb_) +}; + +static void PTRCALL +ascii_toUtf8(const ENCODING *enc, + const char **fromP, const char *fromLim, + char **toP, const char *toLim) +{ + while (*fromP != fromLim && *toP != toLim) + *(*toP)++ = *(*fromP)++; +} + +#ifdef XML_NS + +static const struct normal_encoding ascii_encoding_ns = { + { VTABLE1, ascii_toUtf8, latin1_toUtf16, 1, 1, 0 }, + { +#include "asciitab.h" +/* BT_NONXML == 0 */ + }, + STANDARD_VTABLE(sb_) +}; + +#endif + +static const struct normal_encoding ascii_encoding = { + { VTABLE1, ascii_toUtf8, latin1_toUtf16, 1, 1, 0 }, + { +#define BT_COLON BT_NMSTRT +#include "asciitab.h" +#undef BT_COLON +/* BT_NONXML == 0 */ + }, + STANDARD_VTABLE(sb_) +}; + +static int PTRFASTCALL +unicode_byte_type(char hi, char lo) +{ + switch ((unsigned char)hi) { + case 0xD8: case 0xD9: case 0xDA: case 0xDB: + return BT_LEAD4; + case 0xDC: case 0xDD: case 0xDE: case 0xDF: + return BT_TRAIL; + case 0xFF: + switch ((unsigned char)lo) { + case 0xFF: + case 0xFE: + return BT_NONXML; + } + break; + } + return BT_NONASCII; +} + +#define DEFINE_UTF16_TO_UTF8(E) \ +static void PTRCALL \ +E ## toUtf8(const ENCODING *enc, \ + const char **fromP, const char *fromLim, \ + char **toP, const char *toLim) \ +{ \ + const char *from; \ + for (from = *fromP; from != fromLim; from += 2) { \ + int plane; \ + unsigned char lo2; \ + unsigned char lo = GET_LO(from); \ + unsigned char hi = GET_HI(from); \ + switch (hi) { \ + case 0: \ + if (lo < 0x80) { \ + if (*toP == toLim) { \ + *fromP = from; \ + return; \ + } \ + *(*toP)++ = lo; \ + break; \ + } \ + /* fall through */ \ + case 0x1: case 0x2: case 0x3: \ + case 0x4: case 0x5: case 0x6: case 0x7: \ + if (toLim - *toP < 2) { \ + *fromP = from; \ + return; \ + } \ + *(*toP)++ = ((lo >> 6) | (hi << 2) | UTF8_cval2); \ + *(*toP)++ = ((lo & 0x3f) | 0x80); \ + break; \ + default: \ + if (toLim - *toP < 3) { \ + *fromP = from; \ + return; \ + } \ + /* 16 bits divided 4, 6, 6 amongst 3 bytes */ \ + *(*toP)++ = ((hi >> 4) | UTF8_cval3); \ + *(*toP)++ = (((hi & 0xf) << 2) | (lo >> 6) | 0x80); \ + *(*toP)++ = ((lo & 0x3f) | 0x80); \ + break; \ + case 0xD8: case 0xD9: case 0xDA: case 0xDB: \ + if (toLim - *toP < 4) { \ + *fromP = from; \ + return; \ + } \ + plane = (((hi & 0x3) << 2) | ((lo >> 6) & 0x3)) + 1; \ + *(*toP)++ = ((plane >> 2) | UTF8_cval4); \ + *(*toP)++ = (((lo >> 2) & 0xF) | ((plane & 0x3) << 4) | 0x80); \ + from += 2; \ + lo2 = GET_LO(from); \ + *(*toP)++ = (((lo & 0x3) << 4) \ + | ((GET_HI(from) & 0x3) << 2) \ + | (lo2 >> 6) \ + | 0x80); \ + *(*toP)++ = ((lo2 & 0x3f) | 0x80); \ + break; \ + } \ + } \ + *fromP = from; \ +} + +#define DEFINE_UTF16_TO_UTF16(E) \ +static void PTRCALL \ +E ## toUtf16(const ENCODING *enc, \ + const char **fromP, const char *fromLim, \ + unsigned short **toP, const unsigned short *toLim) \ +{ \ + /* Avoid copying first half only of surrogate */ \ + if (fromLim - *fromP > ((toLim - *toP) << 1) \ + && (GET_HI(fromLim - 2) & 0xF8) == 0xD8) \ + fromLim -= 2; \ + for (; *fromP != fromLim && *toP != toLim; *fromP += 2) \ + *(*toP)++ = (GET_HI(*fromP) << 8) | GET_LO(*fromP); \ +} + +#define SET2(ptr, ch) \ + (((ptr)[0] = ((ch) & 0xff)), ((ptr)[1] = ((ch) >> 8))) +#define GET_LO(ptr) ((unsigned char)(ptr)[0]) +#define GET_HI(ptr) ((unsigned char)(ptr)[1]) + +DEFINE_UTF16_TO_UTF8(little2_) +DEFINE_UTF16_TO_UTF16(little2_) + +#undef SET2 +#undef GET_LO +#undef GET_HI + +#define SET2(ptr, ch) \ + (((ptr)[0] = ((ch) >> 8)), ((ptr)[1] = ((ch) & 0xFF))) +#define GET_LO(ptr) ((unsigned char)(ptr)[1]) +#define GET_HI(ptr) ((unsigned char)(ptr)[0]) + +DEFINE_UTF16_TO_UTF8(big2_) +DEFINE_UTF16_TO_UTF16(big2_) + +#undef SET2 +#undef GET_LO +#undef GET_HI + +#define LITTLE2_BYTE_TYPE(enc, p) \ + ((p)[1] == 0 \ + ? ((struct normal_encoding *)(enc))->type[(unsigned char)*(p)] \ + : unicode_byte_type((p)[1], (p)[0])) +#define LITTLE2_BYTE_TO_ASCII(enc, p) ((p)[1] == 0 ? (p)[0] : -1) +#define LITTLE2_CHAR_MATCHES(enc, p, c) ((p)[1] == 0 && (p)[0] == c) +#define LITTLE2_IS_NAME_CHAR_MINBPC(enc, p) \ + UCS2_GET_NAMING(namePages, (unsigned char)p[1], (unsigned char)p[0]) +#define LITTLE2_IS_NMSTRT_CHAR_MINBPC(enc, p) \ + UCS2_GET_NAMING(nmstrtPages, (unsigned char)p[1], (unsigned char)p[0]) + +#ifdef XML_MIN_SIZE + +static int PTRFASTCALL +little2_byteType(const ENCODING *enc, const char *p) +{ + return LITTLE2_BYTE_TYPE(enc, p); +} + +static int PTRFASTCALL +little2_byteToAscii(const ENCODING *enc, const char *p) +{ + return LITTLE2_BYTE_TO_ASCII(enc, p); +} + +static int PTRCALL +little2_charMatches(const ENCODING *enc, const char *p, int c) +{ + return LITTLE2_CHAR_MATCHES(enc, p, c); +} + +static int PTRFASTCALL +little2_isNameMin(const ENCODING *enc, const char *p) +{ + return LITTLE2_IS_NAME_CHAR_MINBPC(enc, p); +} + +static int PTRFASTCALL +little2_isNmstrtMin(const ENCODING *enc, const char *p) +{ + return LITTLE2_IS_NMSTRT_CHAR_MINBPC(enc, p); +} + +#undef VTABLE +#define VTABLE VTABLE1, little2_toUtf8, little2_toUtf16 + +#else /* not XML_MIN_SIZE */ + +#undef PREFIX +#define PREFIX(ident) little2_ ## ident +#define MINBPC(enc) 2 +/* CHAR_MATCHES is guaranteed to have MINBPC bytes available. */ +#define BYTE_TYPE(enc, p) LITTLE2_BYTE_TYPE(enc, p) +#define BYTE_TO_ASCII(enc, p) LITTLE2_BYTE_TO_ASCII(enc, p) +#define CHAR_MATCHES(enc, p, c) LITTLE2_CHAR_MATCHES(enc, p, c) +#define IS_NAME_CHAR(enc, p, n) 0 +#define IS_NAME_CHAR_MINBPC(enc, p) LITTLE2_IS_NAME_CHAR_MINBPC(enc, p) +#define IS_NMSTRT_CHAR(enc, p, n) (0) +#define IS_NMSTRT_CHAR_MINBPC(enc, p) LITTLE2_IS_NMSTRT_CHAR_MINBPC(enc, p) + +#include "xmltok_impl.c" + +#undef MINBPC +#undef BYTE_TYPE +#undef BYTE_TO_ASCII +#undef CHAR_MATCHES +#undef IS_NAME_CHAR +#undef IS_NAME_CHAR_MINBPC +#undef IS_NMSTRT_CHAR +#undef IS_NMSTRT_CHAR_MINBPC +#undef IS_INVALID_CHAR + +#endif /* not XML_MIN_SIZE */ + +#ifdef XML_NS + +static const struct normal_encoding little2_encoding_ns = { + { VTABLE, 2, 0, +#if BYTEORDER == 1234 + 1 +#else + 0 +#endif + }, + { +#include "asciitab.h" +#include "latin1tab.h" + }, + STANDARD_VTABLE(little2_) +}; + +#endif + +static const struct normal_encoding little2_encoding = { + { VTABLE, 2, 0, +#if BYTEORDER == 1234 + 1 +#else + 0 +#endif + }, + { +#define BT_COLON BT_NMSTRT +#include "asciitab.h" +#undef BT_COLON +#include "latin1tab.h" + }, + STANDARD_VTABLE(little2_) +}; + +#if BYTEORDER != 4321 + +#ifdef XML_NS + +static const struct normal_encoding internal_little2_encoding_ns = { + { VTABLE, 2, 0, 1 }, + { +#include "iasciitab.h" +#include "latin1tab.h" + }, + STANDARD_VTABLE(little2_) +}; + +#endif + +static const struct normal_encoding internal_little2_encoding = { + { VTABLE, 2, 0, 1 }, + { +#define BT_COLON BT_NMSTRT +#include "iasciitab.h" +#undef BT_COLON +#include "latin1tab.h" + }, + STANDARD_VTABLE(little2_) +}; + +#endif + + +#define BIG2_BYTE_TYPE(enc, p) \ + ((p)[0] == 0 \ + ? ((struct normal_encoding *)(enc))->type[(unsigned char)(p)[1]] \ + : unicode_byte_type((p)[0], (p)[1])) +#define BIG2_BYTE_TO_ASCII(enc, p) ((p)[0] == 0 ? (p)[1] : -1) +#define BIG2_CHAR_MATCHES(enc, p, c) ((p)[0] == 0 && (p)[1] == c) +#define BIG2_IS_NAME_CHAR_MINBPC(enc, p) \ + UCS2_GET_NAMING(namePages, (unsigned char)p[0], (unsigned char)p[1]) +#define BIG2_IS_NMSTRT_CHAR_MINBPC(enc, p) \ + UCS2_GET_NAMING(nmstrtPages, (unsigned char)p[0], (unsigned char)p[1]) + +#ifdef XML_MIN_SIZE + +static int PTRFASTCALL +big2_byteType(const ENCODING *enc, const char *p) +{ + return BIG2_BYTE_TYPE(enc, p); +} + +static int PTRFASTCALL +big2_byteToAscii(const ENCODING *enc, const char *p) +{ + return BIG2_BYTE_TO_ASCII(enc, p); +} + +static int PTRCALL +big2_charMatches(const ENCODING *enc, const char *p, int c) +{ + return BIG2_CHAR_MATCHES(enc, p, c); +} + +static int PTRFASTCALL +big2_isNameMin(const ENCODING *enc, const char *p) +{ + return BIG2_IS_NAME_CHAR_MINBPC(enc, p); +} + +static int PTRFASTCALL +big2_isNmstrtMin(const ENCODING *enc, const char *p) +{ + return BIG2_IS_NMSTRT_CHAR_MINBPC(enc, p); +} + +#undef VTABLE +#define VTABLE VTABLE1, big2_toUtf8, big2_toUtf16 + +#else /* not XML_MIN_SIZE */ + +#undef PREFIX +#define PREFIX(ident) big2_ ## ident +#define MINBPC(enc) 2 +/* CHAR_MATCHES is guaranteed to have MINBPC bytes available. */ +#define BYTE_TYPE(enc, p) BIG2_BYTE_TYPE(enc, p) +#define BYTE_TO_ASCII(enc, p) BIG2_BYTE_TO_ASCII(enc, p) +#define CHAR_MATCHES(enc, p, c) BIG2_CHAR_MATCHES(enc, p, c) +#define IS_NAME_CHAR(enc, p, n) 0 +#define IS_NAME_CHAR_MINBPC(enc, p) BIG2_IS_NAME_CHAR_MINBPC(enc, p) +#define IS_NMSTRT_CHAR(enc, p, n) (0) +#define IS_NMSTRT_CHAR_MINBPC(enc, p) BIG2_IS_NMSTRT_CHAR_MINBPC(enc, p) + +#include "xmltok_impl.c" + +#undef MINBPC +#undef BYTE_TYPE +#undef BYTE_TO_ASCII +#undef CHAR_MATCHES +#undef IS_NAME_CHAR +#undef IS_NAME_CHAR_MINBPC +#undef IS_NMSTRT_CHAR +#undef IS_NMSTRT_CHAR_MINBPC +#undef IS_INVALID_CHAR + +#endif /* not XML_MIN_SIZE */ + +#ifdef XML_NS + +static const struct normal_encoding big2_encoding_ns = { + { VTABLE, 2, 0, +#if BYTEORDER == 4321 + 1 +#else + 0 +#endif + }, + { +#include "asciitab.h" +#include "latin1tab.h" + }, + STANDARD_VTABLE(big2_) +}; + +#endif + +static const struct normal_encoding big2_encoding = { + { VTABLE, 2, 0, +#if BYTEORDER == 4321 + 1 +#else + 0 +#endif + }, + { +#define BT_COLON BT_NMSTRT +#include "asciitab.h" +#undef BT_COLON +#include "latin1tab.h" + }, + STANDARD_VTABLE(big2_) +}; + +#if BYTEORDER != 1234 + +#ifdef XML_NS + +static const struct normal_encoding internal_big2_encoding_ns = { + { VTABLE, 2, 0, 1 }, + { +#include "iasciitab.h" +#include "latin1tab.h" + }, + STANDARD_VTABLE(big2_) +}; + +#endif + +static const struct normal_encoding internal_big2_encoding = { + { VTABLE, 2, 0, 1 }, + { +#define BT_COLON BT_NMSTRT +#include "iasciitab.h" +#undef BT_COLON +#include "latin1tab.h" + }, + STANDARD_VTABLE(big2_) +}; + +#endif + +#undef PREFIX + +static int FASTCALL +streqci(const char *s1, const char *s2) +{ + for (;;) { + char c1 = *s1++; + char c2 = *s2++; + if (ASCII_a <= c1 && c1 <= ASCII_z) + c1 += ASCII_A - ASCII_a; + if (ASCII_a <= c2 && c2 <= ASCII_z) + c2 += ASCII_A - ASCII_a; + if (c1 != c2) + return 0; + if (!c1) + break; + } + return 1; +} + +static void PTRCALL +initUpdatePosition(const ENCODING *enc, const char *ptr, + const char *end, POSITION *pos) +{ + normal_updatePosition(&utf8_encoding.enc, ptr, end, pos); +} + +static int +toAscii(const ENCODING *enc, const char *ptr, const char *end) +{ + char buf[1]; + char *p = buf; + XmlUtf8Convert(enc, &ptr, end, &p, p + 1); + if (p == buf) + return -1; + else + return buf[0]; +} + +static int FASTCALL +isSpace(int c) +{ + switch (c) { + case 0x20: + case 0xD: + case 0xA: + case 0x9: + return 1; + } + return 0; +} + +/* Return 1 if there's just optional white space or there's an S + followed by name=val. +*/ +static int +parsePseudoAttribute(const ENCODING *enc, + const char *ptr, + const char *end, + const char **namePtr, + const char **nameEndPtr, + const char **valPtr, + const char **nextTokPtr) +{ + int c; + char open; + if (ptr == end) { + *namePtr = NULL; + return 1; + } + if (!isSpace(toAscii(enc, ptr, end))) { + *nextTokPtr = ptr; + return 0; + } + do { + ptr += enc->minBytesPerChar; + } while (isSpace(toAscii(enc, ptr, end))); + if (ptr == end) { + *namePtr = NULL; + return 1; + } + *namePtr = ptr; + for (;;) { + c = toAscii(enc, ptr, end); + if (c == -1) { + *nextTokPtr = ptr; + return 0; + } + if (c == ASCII_EQUALS) { + *nameEndPtr = ptr; + break; + } + if (isSpace(c)) { + *nameEndPtr = ptr; + do { + ptr += enc->minBytesPerChar; + } while (isSpace(c = toAscii(enc, ptr, end))); + if (c != ASCII_EQUALS) { + *nextTokPtr = ptr; + return 0; + } + break; + } + ptr += enc->minBytesPerChar; + } + if (ptr == *namePtr) { + *nextTokPtr = ptr; + return 0; + } + ptr += enc->minBytesPerChar; + c = toAscii(enc, ptr, end); + while (isSpace(c)) { + ptr += enc->minBytesPerChar; + c = toAscii(enc, ptr, end); + } + if (c != ASCII_QUOT && c != ASCII_APOS) { + *nextTokPtr = ptr; + return 0; + } + open = (char)c; + ptr += enc->minBytesPerChar; + *valPtr = ptr; + for (;; ptr += enc->minBytesPerChar) { + c = toAscii(enc, ptr, end); + if (c == open) + break; + if (!(ASCII_a <= c && c <= ASCII_z) + && !(ASCII_A <= c && c <= ASCII_Z) + && !(ASCII_0 <= c && c <= ASCII_9) + && c != ASCII_PERIOD + && c != ASCII_MINUS + && c != ASCII_UNDERSCORE) { + *nextTokPtr = ptr; + return 0; + } + } + *nextTokPtr = ptr + enc->minBytesPerChar; + return 1; +} + +static const char KW_version[] = { + ASCII_v, ASCII_e, ASCII_r, ASCII_s, ASCII_i, ASCII_o, ASCII_n, '\0' +}; + +static const char KW_encoding[] = { + ASCII_e, ASCII_n, ASCII_c, ASCII_o, ASCII_d, ASCII_i, ASCII_n, ASCII_g, '\0' +}; + +static const char KW_standalone[] = { + ASCII_s, ASCII_t, ASCII_a, ASCII_n, ASCII_d, ASCII_a, ASCII_l, ASCII_o, + ASCII_n, ASCII_e, '\0' +}; + +static const char KW_yes[] = { + ASCII_y, ASCII_e, ASCII_s, '\0' +}; + +static const char KW_no[] = { + ASCII_n, ASCII_o, '\0' +}; + +static int +doParseXmlDecl(const ENCODING *(*encodingFinder)(const ENCODING *, + const char *, + const char *), + int isGeneralTextEntity, + const ENCODING *enc, + const char *ptr, + const char *end, + const char **badPtr, + const char **versionPtr, + const char **versionEndPtr, + const char **encodingName, + const ENCODING **encoding, + int *standalone) +{ + const char *val = NULL; + const char *name = NULL; + const char *nameEnd = NULL; + ptr += 5 * enc->minBytesPerChar; + end -= 2 * enc->minBytesPerChar; + if (!parsePseudoAttribute(enc, ptr, end, &name, &nameEnd, &val, &ptr) + || !name) { + *badPtr = ptr; + return 0; + } + if (!XmlNameMatchesAscii(enc, name, nameEnd, KW_version)) { + if (!isGeneralTextEntity) { + *badPtr = name; + return 0; + } + } + else { + if (versionPtr) + *versionPtr = val; + if (versionEndPtr) + *versionEndPtr = ptr; + if (!parsePseudoAttribute(enc, ptr, end, &name, &nameEnd, &val, &ptr)) { + *badPtr = ptr; + return 0; + } + if (!name) { + if (isGeneralTextEntity) { + /* a TextDecl must have an EncodingDecl */ + *badPtr = ptr; + return 0; + } + return 1; + } + } + if (XmlNameMatchesAscii(enc, name, nameEnd, KW_encoding)) { + int c = toAscii(enc, val, end); + if (!(ASCII_a <= c && c <= ASCII_z) && !(ASCII_A <= c && c <= ASCII_Z)) { + *badPtr = val; + return 0; + } + if (encodingName) + *encodingName = val; + if (encoding) + *encoding = encodingFinder(enc, val, ptr - enc->minBytesPerChar); + if (!parsePseudoAttribute(enc, ptr, end, &name, &nameEnd, &val, &ptr)) { + *badPtr = ptr; + return 0; + } + if (!name) + return 1; + } + if (!XmlNameMatchesAscii(enc, name, nameEnd, KW_standalone) + || isGeneralTextEntity) { + *badPtr = name; + return 0; + } + if (XmlNameMatchesAscii(enc, val, ptr - enc->minBytesPerChar, KW_yes)) { + if (standalone) + *standalone = 1; + } + else if (XmlNameMatchesAscii(enc, val, ptr - enc->minBytesPerChar, KW_no)) { + if (standalone) + *standalone = 0; + } + else { + *badPtr = val; + return 0; + } + while (isSpace(toAscii(enc, ptr, end))) + ptr += enc->minBytesPerChar; + if (ptr != end) { + *badPtr = ptr; + return 0; + } + return 1; +} + +static int FASTCALL +checkCharRefNumber(int result) +{ + switch (result >> 8) { + case 0xD8: case 0xD9: case 0xDA: case 0xDB: + case 0xDC: case 0xDD: case 0xDE: case 0xDF: + return -1; + case 0: + if (latin1_encoding.type[result] == BT_NONXML) + return -1; + break; + case 0xFF: + if (result == 0xFFFE || result == 0xFFFF) + return -1; + break; + } + return result; +} + +int FASTCALL +XmlUtf8Encode(int c, char *buf) +{ + enum { + /* minN is minimum legal resulting value for N byte sequence */ + min2 = 0x80, + min3 = 0x800, + min4 = 0x10000 + }; + + if (c < 0) + return 0; + if (c < min2) { + buf[0] = (char)(c | UTF8_cval1); + return 1; + } + if (c < min3) { + buf[0] = (char)((c >> 6) | UTF8_cval2); + buf[1] = (char)((c & 0x3f) | 0x80); + return 2; + } + if (c < min4) { + buf[0] = (char)((c >> 12) | UTF8_cval3); + buf[1] = (char)(((c >> 6) & 0x3f) | 0x80); + buf[2] = (char)((c & 0x3f) | 0x80); + return 3; + } + if (c < 0x110000) { + buf[0] = (char)((c >> 18) | UTF8_cval4); + buf[1] = (char)(((c >> 12) & 0x3f) | 0x80); + buf[2] = (char)(((c >> 6) & 0x3f) | 0x80); + buf[3] = (char)((c & 0x3f) | 0x80); + return 4; + } + return 0; +} + +int FASTCALL +XmlUtf16Encode(int charNum, unsigned short *buf) +{ + if (charNum < 0) + return 0; + if (charNum < 0x10000) { + buf[0] = (unsigned short)charNum; + return 1; + } + if (charNum < 0x110000) { + charNum -= 0x10000; + buf[0] = (unsigned short)((charNum >> 10) + 0xD800); + buf[1] = (unsigned short)((charNum & 0x3FF) + 0xDC00); + return 2; + } + return 0; +} + +struct unknown_encoding { + struct normal_encoding normal; + int (*convert)(void *userData, const char *p); + void *userData; + unsigned short utf16[256]; + char utf8[256][4]; +}; + +#define AS_UNKNOWN_ENCODING(enc) ((const struct unknown_encoding *) (enc)) + +int +XmlSizeOfUnknownEncoding(void) +{ + return sizeof(struct unknown_encoding); +} + +static int PTRFASTCALL +unknown_isName(const ENCODING *enc, const char *p) +{ + const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); + int c = uenc->convert(uenc->userData, p); + if (c & ~0xFFFF) + return 0; + return UCS2_GET_NAMING(namePages, c >> 8, c & 0xFF); +} + +static int PTRFASTCALL +unknown_isNmstrt(const ENCODING *enc, const char *p) +{ + const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); + int c = uenc->convert(uenc->userData, p); + if (c & ~0xFFFF) + return 0; + return UCS2_GET_NAMING(nmstrtPages, c >> 8, c & 0xFF); +} + +static int PTRFASTCALL +unknown_isInvalid(const ENCODING *enc, const char *p) +{ + const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); + int c = uenc->convert(uenc->userData, p); + return (c & ~0xFFFF) || checkCharRefNumber(c) < 0; +} + +static void PTRCALL +unknown_toUtf8(const ENCODING *enc, + const char **fromP, const char *fromLim, + char **toP, const char *toLim) +{ + const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); + char buf[XML_UTF8_ENCODE_MAX]; + for (;;) { + const char *utf8; + int n; + if (*fromP == fromLim) + break; + utf8 = uenc->utf8[(unsigned char)**fromP]; + n = *utf8++; + if (n == 0) { + int c = uenc->convert(uenc->userData, *fromP); + n = XmlUtf8Encode(c, buf); + if (n > toLim - *toP) + break; + utf8 = buf; + *fromP += (AS_NORMAL_ENCODING(enc)->type[(unsigned char)**fromP] + - (BT_LEAD2 - 2)); + } + else { + if (n > toLim - *toP) + break; + (*fromP)++; + } + do { + *(*toP)++ = *utf8++; + } while (--n != 0); + } +} + +static void PTRCALL +unknown_toUtf16(const ENCODING *enc, + const char **fromP, const char *fromLim, + unsigned short **toP, const unsigned short *toLim) +{ + const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); + while (*fromP != fromLim && *toP != toLim) { + unsigned short c = uenc->utf16[(unsigned char)**fromP]; + if (c == 0) { + c = (unsigned short) + uenc->convert(uenc->userData, *fromP); + *fromP += (AS_NORMAL_ENCODING(enc)->type[(unsigned char)**fromP] + - (BT_LEAD2 - 2)); + } + else + (*fromP)++; + *(*toP)++ = c; + } +} + +ENCODING * +XmlInitUnknownEncoding(void *mem, + int *table, + CONVERTER convert, + void *userData) +{ + int i; + struct unknown_encoding *e = (struct unknown_encoding *)mem; + for (i = 0; i < (int)sizeof(struct normal_encoding); i++) + ((char *)mem)[i] = ((char *)&latin1_encoding)[i]; + for (i = 0; i < 128; i++) + if (latin1_encoding.type[i] != BT_OTHER + && latin1_encoding.type[i] != BT_NONXML + && table[i] != i) + return 0; + for (i = 0; i < 256; i++) { + int c = table[i]; + if (c == -1) { + e->normal.type[i] = BT_MALFORM; + /* This shouldn't really get used. */ + e->utf16[i] = 0xFFFF; + e->utf8[i][0] = 1; + e->utf8[i][1] = 0; + } + else if (c < 0) { + if (c < -4) + return 0; + e->normal.type[i] = (unsigned char)(BT_LEAD2 - (c + 2)); + e->utf8[i][0] = 0; + e->utf16[i] = 0; + } + else if (c < 0x80) { + if (latin1_encoding.type[c] != BT_OTHER + && latin1_encoding.type[c] != BT_NONXML + && c != i) + return 0; + e->normal.type[i] = latin1_encoding.type[c]; + e->utf8[i][0] = 1; + e->utf8[i][1] = (char)c; + e->utf16[i] = (unsigned short)(c == 0 ? 0xFFFF : c); + } + else if (checkCharRefNumber(c) < 0) { + e->normal.type[i] = BT_NONXML; + /* This shouldn't really get used. */ + e->utf16[i] = 0xFFFF; + e->utf8[i][0] = 1; + e->utf8[i][1] = 0; + } + else { + if (c > 0xFFFF) + return 0; + if (UCS2_GET_NAMING(nmstrtPages, c >> 8, c & 0xff)) + e->normal.type[i] = BT_NMSTRT; + else if (UCS2_GET_NAMING(namePages, c >> 8, c & 0xff)) + e->normal.type[i] = BT_NAME; + else + e->normal.type[i] = BT_OTHER; + e->utf8[i][0] = (char)XmlUtf8Encode(c, e->utf8[i] + 1); + e->utf16[i] = (unsigned short)c; + } + } + e->userData = userData; + e->convert = convert; + if (convert) { + e->normal.isName2 = unknown_isName; + e->normal.isName3 = unknown_isName; + e->normal.isName4 = unknown_isName; + e->normal.isNmstrt2 = unknown_isNmstrt; + e->normal.isNmstrt3 = unknown_isNmstrt; + e->normal.isNmstrt4 = unknown_isNmstrt; + e->normal.isInvalid2 = unknown_isInvalid; + e->normal.isInvalid3 = unknown_isInvalid; + e->normal.isInvalid4 = unknown_isInvalid; + } + e->normal.enc.utf8Convert = unknown_toUtf8; + e->normal.enc.utf16Convert = unknown_toUtf16; + return &(e->normal.enc); +} + +/* If this enumeration is changed, getEncodingIndex and encodings +must also be changed. */ +enum { + UNKNOWN_ENC = -1, + ISO_8859_1_ENC = 0, + US_ASCII_ENC, + UTF_8_ENC, + UTF_16_ENC, + UTF_16BE_ENC, + UTF_16LE_ENC, + /* must match encodingNames up to here */ + NO_ENC +}; + +static const char KW_ISO_8859_1[] = { + ASCII_I, ASCII_S, ASCII_O, ASCII_MINUS, ASCII_8, ASCII_8, ASCII_5, ASCII_9, + ASCII_MINUS, ASCII_1, '\0' +}; +static const char KW_US_ASCII[] = { + ASCII_U, ASCII_S, ASCII_MINUS, ASCII_A, ASCII_S, ASCII_C, ASCII_I, ASCII_I, + '\0' +}; +static const char KW_UTF_8[] = { + ASCII_U, ASCII_T, ASCII_F, ASCII_MINUS, ASCII_8, '\0' +}; +static const char KW_UTF_16[] = { + ASCII_U, ASCII_T, ASCII_F, ASCII_MINUS, ASCII_1, ASCII_6, '\0' +}; +static const char KW_UTF_16BE[] = { + ASCII_U, ASCII_T, ASCII_F, ASCII_MINUS, ASCII_1, ASCII_6, ASCII_B, ASCII_E, + '\0' +}; +static const char KW_UTF_16LE[] = { + ASCII_U, ASCII_T, ASCII_F, ASCII_MINUS, ASCII_1, ASCII_6, ASCII_L, ASCII_E, + '\0' +}; + +static int FASTCALL +getEncodingIndex(const char *name) +{ + static const char *encodingNames[] = { + KW_ISO_8859_1, + KW_US_ASCII, + KW_UTF_8, + KW_UTF_16, + KW_UTF_16BE, + KW_UTF_16LE, + }; + int i; + if (name == NULL) + return NO_ENC; + for (i = 0; i < (int)(sizeof(encodingNames)/sizeof(encodingNames[0])); i++) + if (streqci(name, encodingNames[i])) + return i; + return UNKNOWN_ENC; +} + +/* For binary compatibility, we store the index of the encoding + specified at initialization in the isUtf16 member. +*/ + +#define INIT_ENC_INDEX(enc) ((int)(enc)->initEnc.isUtf16) +#define SET_INIT_ENC_INDEX(enc, i) ((enc)->initEnc.isUtf16 = (char)i) + +/* This is what detects the encoding. encodingTable maps from + encoding indices to encodings; INIT_ENC_INDEX(enc) is the index of + the external (protocol) specified encoding; state is + XML_CONTENT_STATE if we're parsing an external text entity, and + XML_PROLOG_STATE otherwise. +*/ + + +static int +initScan(const ENCODING **encodingTable, + const INIT_ENCODING *enc, + int state, + const char *ptr, + const char *end, + const char **nextTokPtr) +{ + const ENCODING **encPtr; + + if (ptr == end) + return XML_TOK_NONE; + encPtr = enc->encPtr; + if (ptr + 1 == end) { + /* only a single byte available for auto-detection */ +#ifndef XML_DTD /* FIXME */ + /* a well-formed document entity must have more than one byte */ + if (state != XML_CONTENT_STATE) + return XML_TOK_PARTIAL; +#endif + /* so we're parsing an external text entity... */ + /* if UTF-16 was externally specified, then we need at least 2 bytes */ + switch (INIT_ENC_INDEX(enc)) { + case UTF_16_ENC: + case UTF_16LE_ENC: + case UTF_16BE_ENC: + return XML_TOK_PARTIAL; + } + switch ((unsigned char)*ptr) { + case 0xFE: + case 0xFF: + case 0xEF: /* possibly first byte of UTF-8 BOM */ + if (INIT_ENC_INDEX(enc) == ISO_8859_1_ENC + && state == XML_CONTENT_STATE) + break; + /* fall through */ + case 0x00: + case 0x3C: + return XML_TOK_PARTIAL; + } + } + else { + switch (((unsigned char)ptr[0] << 8) | (unsigned char)ptr[1]) { + case 0xFEFF: + if (INIT_ENC_INDEX(enc) == ISO_8859_1_ENC + && state == XML_CONTENT_STATE) + break; + *nextTokPtr = ptr + 2; + *encPtr = encodingTable[UTF_16BE_ENC]; + return XML_TOK_BOM; + /* 00 3C is handled in the default case */ + case 0x3C00: + if ((INIT_ENC_INDEX(enc) == UTF_16BE_ENC + || INIT_ENC_INDEX(enc) == UTF_16_ENC) + && state == XML_CONTENT_STATE) + break; + *encPtr = encodingTable[UTF_16LE_ENC]; + return XmlTok(*encPtr, state, ptr, end, nextTokPtr); + case 0xFFFE: + if (INIT_ENC_INDEX(enc) == ISO_8859_1_ENC + && state == XML_CONTENT_STATE) + break; + *nextTokPtr = ptr + 2; + *encPtr = encodingTable[UTF_16LE_ENC]; + return XML_TOK_BOM; + case 0xEFBB: + /* Maybe a UTF-8 BOM (EF BB BF) */ + /* If there's an explicitly specified (external) encoding + of ISO-8859-1 or some flavour of UTF-16 + and this is an external text entity, + don't look for the BOM, + because it might be a legal data. + */ + if (state == XML_CONTENT_STATE) { + int e = INIT_ENC_INDEX(enc); + if (e == ISO_8859_1_ENC || e == UTF_16BE_ENC + || e == UTF_16LE_ENC || e == UTF_16_ENC) + break; + } + if (ptr + 2 == end) + return XML_TOK_PARTIAL; + if ((unsigned char)ptr[2] == 0xBF) { + *nextTokPtr = ptr + 3; + *encPtr = encodingTable[UTF_8_ENC]; + return XML_TOK_BOM; + } + break; + default: + if (ptr[0] == '\0') { + /* 0 isn't a legal data character. Furthermore a document + entity can only start with ASCII characters. So the only + way this can fail to be big-endian UTF-16 if it it's an + external parsed general entity that's labelled as + UTF-16LE. + */ + if (state == XML_CONTENT_STATE && INIT_ENC_INDEX(enc) == UTF_16LE_ENC) + break; + *encPtr = encodingTable[UTF_16BE_ENC]; + return XmlTok(*encPtr, state, ptr, end, nextTokPtr); + } + else if (ptr[1] == '\0') { + /* We could recover here in the case: + - parsing an external entity + - second byte is 0 + - no externally specified encoding + - no encoding declaration + by assuming UTF-16LE. But we don't, because this would mean when + presented just with a single byte, we couldn't reliably determine + whether we needed further bytes. + */ + if (state == XML_CONTENT_STATE) + break; + *encPtr = encodingTable[UTF_16LE_ENC]; + return XmlTok(*encPtr, state, ptr, end, nextTokPtr); + } + break; + } + } + *encPtr = encodingTable[INIT_ENC_INDEX(enc)]; + return XmlTok(*encPtr, state, ptr, end, nextTokPtr); +} + + +#define NS(x) x +#define ns(x) x +#include "xmltok_ns.c" +#undef NS +#undef ns + +#ifdef XML_NS + +#define NS(x) x ## NS +#define ns(x) x ## _ns + +#include "xmltok_ns.c" + +#undef NS +#undef ns + +ENCODING * +XmlInitUnknownEncodingNS(void *mem, + int *table, + CONVERTER convert, + void *userData) +{ + ENCODING *enc = XmlInitUnknownEncoding(mem, table, convert, userData); + if (enc) + ((struct normal_encoding *)enc)->type[ASCII_COLON] = BT_COLON; + return enc; +} + +#endif /* XML_NS */ diff --git a/expat/xmltok.h b/expat/xmltok.h new file mode 100644 index 00000000..3d776be7 --- /dev/null +++ b/expat/xmltok.h @@ -0,0 +1,315 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +#ifndef XmlTok_INCLUDED +#define XmlTok_INCLUDED 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* The following token may be returned by XmlContentTok */ +#define XML_TOK_TRAILING_RSQB -5 /* ] or ]] at the end of the scan; might be + start of illegal ]]> sequence */ +/* The following tokens may be returned by both XmlPrologTok and + XmlContentTok. +*/ +#define XML_TOK_NONE -4 /* The string to be scanned is empty */ +#define XML_TOK_TRAILING_CR -3 /* A CR at the end of the scan; + might be part of CRLF sequence */ +#define XML_TOK_PARTIAL_CHAR -2 /* only part of a multibyte sequence */ +#define XML_TOK_PARTIAL -1 /* only part of a token */ +#define XML_TOK_INVALID 0 + +/* The following tokens are returned by XmlContentTok; some are also + returned by XmlAttributeValueTok, XmlEntityTok, XmlCdataSectionTok. +*/ +#define XML_TOK_START_TAG_WITH_ATTS 1 +#define XML_TOK_START_TAG_NO_ATTS 2 +#define XML_TOK_EMPTY_ELEMENT_WITH_ATTS 3 /* empty element tag */ +#define XML_TOK_EMPTY_ELEMENT_NO_ATTS 4 +#define XML_TOK_END_TAG 5 +#define XML_TOK_DATA_CHARS 6 +#define XML_TOK_DATA_NEWLINE 7 +#define XML_TOK_CDATA_SECT_OPEN 8 +#define XML_TOK_ENTITY_REF 9 +#define XML_TOK_CHAR_REF 10 /* numeric character reference */ + +/* The following tokens may be returned by both XmlPrologTok and + XmlContentTok. +*/ +#define XML_TOK_PI 11 /* processing instruction */ +#define XML_TOK_XML_DECL 12 /* XML decl or text decl */ +#define XML_TOK_COMMENT 13 +#define XML_TOK_BOM 14 /* Byte order mark */ + +/* The following tokens are returned only by XmlPrologTok */ +#define XML_TOK_PROLOG_S 15 +#define XML_TOK_DECL_OPEN 16 /* */ +#define XML_TOK_NAME 18 +#define XML_TOK_NMTOKEN 19 +#define XML_TOK_POUND_NAME 20 /* #name */ +#define XML_TOK_OR 21 /* | */ +#define XML_TOK_PERCENT 22 +#define XML_TOK_OPEN_PAREN 23 +#define XML_TOK_CLOSE_PAREN 24 +#define XML_TOK_OPEN_BRACKET 25 +#define XML_TOK_CLOSE_BRACKET 26 +#define XML_TOK_LITERAL 27 +#define XML_TOK_PARAM_ENTITY_REF 28 +#define XML_TOK_INSTANCE_START 29 + +/* The following occur only in element type declarations */ +#define XML_TOK_NAME_QUESTION 30 /* name? */ +#define XML_TOK_NAME_ASTERISK 31 /* name* */ +#define XML_TOK_NAME_PLUS 32 /* name+ */ +#define XML_TOK_COND_SECT_OPEN 33 /* */ +#define XML_TOK_CLOSE_PAREN_QUESTION 35 /* )? */ +#define XML_TOK_CLOSE_PAREN_ASTERISK 36 /* )* */ +#define XML_TOK_CLOSE_PAREN_PLUS 37 /* )+ */ +#define XML_TOK_COMMA 38 + +/* The following token is returned only by XmlAttributeValueTok */ +#define XML_TOK_ATTRIBUTE_VALUE_S 39 + +/* The following token is returned only by XmlCdataSectionTok */ +#define XML_TOK_CDATA_SECT_CLOSE 40 + +/* With namespace processing this is returned by XmlPrologTok for a + name with a colon. +*/ +#define XML_TOK_PREFIXED_NAME 41 + +#ifdef XML_DTD +#define XML_TOK_IGNORE_SECT 42 +#endif /* XML_DTD */ + +#ifdef XML_DTD +#define XML_N_STATES 4 +#else /* not XML_DTD */ +#define XML_N_STATES 3 +#endif /* not XML_DTD */ + +#define XML_PROLOG_STATE 0 +#define XML_CONTENT_STATE 1 +#define XML_CDATA_SECTION_STATE 2 +#ifdef XML_DTD +#define XML_IGNORE_SECTION_STATE 3 +#endif /* XML_DTD */ + +#define XML_N_LITERAL_TYPES 2 +#define XML_ATTRIBUTE_VALUE_LITERAL 0 +#define XML_ENTITY_VALUE_LITERAL 1 + +/* The size of the buffer passed to XmlUtf8Encode must be at least this. */ +#define XML_UTF8_ENCODE_MAX 4 +/* The size of the buffer passed to XmlUtf16Encode must be at least this. */ +#define XML_UTF16_ENCODE_MAX 2 + +typedef struct position { + /* first line and first column are 0 not 1 */ + unsigned long lineNumber; + unsigned long columnNumber; +} POSITION; + +typedef struct { + const char *name; + const char *valuePtr; + const char *valueEnd; + char normalized; +} ATTRIBUTE; + +struct encoding; +typedef struct encoding ENCODING; + +typedef int (PTRCALL *SCANNER)(const ENCODING *, + const char *, + const char *, + const char **); + +struct encoding { + SCANNER scanners[XML_N_STATES]; + SCANNER literalScanners[XML_N_LITERAL_TYPES]; + int (PTRCALL *sameName)(const ENCODING *, + const char *, + const char *); + int (PTRCALL *nameMatchesAscii)(const ENCODING *, + const char *, + const char *, + const char *); + int (PTRFASTCALL *nameLength)(const ENCODING *, const char *); + const char *(PTRFASTCALL *skipS)(const ENCODING *, const char *); + int (PTRCALL *getAtts)(const ENCODING *enc, + const char *ptr, + int attsMax, + ATTRIBUTE *atts); + int (PTRFASTCALL *charRefNumber)(const ENCODING *enc, const char *ptr); + int (PTRCALL *predefinedEntityName)(const ENCODING *, + const char *, + const char *); + void (PTRCALL *updatePosition)(const ENCODING *, + const char *ptr, + const char *end, + POSITION *); + int (PTRCALL *isPublicId)(const ENCODING *enc, + const char *ptr, + const char *end, + const char **badPtr); + void (PTRCALL *utf8Convert)(const ENCODING *enc, + const char **fromP, + const char *fromLim, + char **toP, + const char *toLim); + void (PTRCALL *utf16Convert)(const ENCODING *enc, + const char **fromP, + const char *fromLim, + unsigned short **toP, + const unsigned short *toLim); + int minBytesPerChar; + char isUtf8; + char isUtf16; +}; + +/* Scan the string starting at ptr until the end of the next complete + token, but do not scan past eptr. Return an integer giving the + type of token. + + Return XML_TOK_NONE when ptr == eptr; nextTokPtr will not be set. + + Return XML_TOK_PARTIAL when the string does not contain a complete + token; nextTokPtr will not be set. + + Return XML_TOK_INVALID when the string does not start a valid + token; nextTokPtr will be set to point to the character which made + the token invalid. + + Otherwise the string starts with a valid token; nextTokPtr will be + set to point to the character following the end of that token. + + Each data character counts as a single token, but adjacent data + characters may be returned together. Similarly for characters in + the prolog outside literals, comments and processing instructions. +*/ + + +#define XmlTok(enc, state, ptr, end, nextTokPtr) \ + (((enc)->scanners[state])(enc, ptr, end, nextTokPtr)) + +#define XmlPrologTok(enc, ptr, end, nextTokPtr) \ + XmlTok(enc, XML_PROLOG_STATE, ptr, end, nextTokPtr) + +#define XmlContentTok(enc, ptr, end, nextTokPtr) \ + XmlTok(enc, XML_CONTENT_STATE, ptr, end, nextTokPtr) + +#define XmlCdataSectionTok(enc, ptr, end, nextTokPtr) \ + XmlTok(enc, XML_CDATA_SECTION_STATE, ptr, end, nextTokPtr) + +#ifdef XML_DTD + +#define XmlIgnoreSectionTok(enc, ptr, end, nextTokPtr) \ + XmlTok(enc, XML_IGNORE_SECTION_STATE, ptr, end, nextTokPtr) + +#endif /* XML_DTD */ + +/* This is used for performing a 2nd-level tokenization on the content + of a literal that has already been returned by XmlTok. +*/ +#define XmlLiteralTok(enc, literalType, ptr, end, nextTokPtr) \ + (((enc)->literalScanners[literalType])(enc, ptr, end, nextTokPtr)) + +#define XmlAttributeValueTok(enc, ptr, end, nextTokPtr) \ + XmlLiteralTok(enc, XML_ATTRIBUTE_VALUE_LITERAL, ptr, end, nextTokPtr) + +#define XmlEntityValueTok(enc, ptr, end, nextTokPtr) \ + XmlLiteralTok(enc, XML_ENTITY_VALUE_LITERAL, ptr, end, nextTokPtr) + +#define XmlSameName(enc, ptr1, ptr2) (((enc)->sameName)(enc, ptr1, ptr2)) + +#define XmlNameMatchesAscii(enc, ptr1, end1, ptr2) \ + (((enc)->nameMatchesAscii)(enc, ptr1, end1, ptr2)) + +#define XmlNameLength(enc, ptr) \ + (((enc)->nameLength)(enc, ptr)) + +#define XmlSkipS(enc, ptr) \ + (((enc)->skipS)(enc, ptr)) + +#define XmlGetAttributes(enc, ptr, attsMax, atts) \ + (((enc)->getAtts)(enc, ptr, attsMax, atts)) + +#define XmlCharRefNumber(enc, ptr) \ + (((enc)->charRefNumber)(enc, ptr)) + +#define XmlPredefinedEntityName(enc, ptr, end) \ + (((enc)->predefinedEntityName)(enc, ptr, end)) + +#define XmlUpdatePosition(enc, ptr, end, pos) \ + (((enc)->updatePosition)(enc, ptr, end, pos)) + +#define XmlIsPublicId(enc, ptr, end, badPtr) \ + (((enc)->isPublicId)(enc, ptr, end, badPtr)) + +#define XmlUtf8Convert(enc, fromP, fromLim, toP, toLim) \ + (((enc)->utf8Convert)(enc, fromP, fromLim, toP, toLim)) + +#define XmlUtf16Convert(enc, fromP, fromLim, toP, toLim) \ + (((enc)->utf16Convert)(enc, fromP, fromLim, toP, toLim)) + +typedef struct { + ENCODING initEnc; + const ENCODING **encPtr; +} INIT_ENCODING; + +int XmlParseXmlDecl(int isGeneralTextEntity, + const ENCODING *enc, + const char *ptr, + const char *end, + const char **badPtr, + const char **versionPtr, + const char **versionEndPtr, + const char **encodingNamePtr, + const ENCODING **namedEncodingPtr, + int *standalonePtr); + +int XmlInitEncoding(INIT_ENCODING *, const ENCODING **, const char *name); +const ENCODING *XmlGetUtf8InternalEncoding(void); +const ENCODING *XmlGetUtf16InternalEncoding(void); +int FASTCALL XmlUtf8Encode(int charNumber, char *buf); +int FASTCALL XmlUtf16Encode(int charNumber, unsigned short *buf); +int XmlSizeOfUnknownEncoding(void); + +typedef int (*CONVERTER)(void *userData, const char *p); + +ENCODING * +XmlInitUnknownEncoding(void *mem, + int *table, + CONVERTER convert, + void *userData); + +int XmlParseXmlDeclNS(int isGeneralTextEntity, + const ENCODING *enc, + const char *ptr, + const char *end, + const char **badPtr, + const char **versionPtr, + const char **versionEndPtr, + const char **encodingNamePtr, + const ENCODING **namedEncodingPtr, + int *standalonePtr); + +int XmlInitEncodingNS(INIT_ENCODING *, const ENCODING **, const char *name); +const ENCODING *XmlGetUtf8InternalEncodingNS(void); +const ENCODING *XmlGetUtf16InternalEncodingNS(void); +ENCODING * +XmlInitUnknownEncodingNS(void *mem, + int *table, + CONVERTER convert, + void *userData); +#ifdef __cplusplus +} +#endif + +#endif /* not XmlTok_INCLUDED */ diff --git a/expat/xmltok_impl.c b/expat/xmltok_impl.c new file mode 100644 index 00000000..46569fe3 --- /dev/null +++ b/expat/xmltok_impl.c @@ -0,0 +1,1779 @@ +/* Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd + See the file COPYING for copying permission. +*/ + +#ifndef IS_INVALID_CHAR +#define IS_INVALID_CHAR(enc, ptr, n) (0) +#endif + +#define INVALID_LEAD_CASE(n, ptr, nextTokPtr) \ + case BT_LEAD ## n: \ + if (end - ptr < n) \ + return XML_TOK_PARTIAL_CHAR; \ + if (IS_INVALID_CHAR(enc, ptr, n)) { \ + *(nextTokPtr) = (ptr); \ + return XML_TOK_INVALID; \ + } \ + ptr += n; \ + break; + +#define INVALID_CASES(ptr, nextTokPtr) \ + INVALID_LEAD_CASE(2, ptr, nextTokPtr) \ + INVALID_LEAD_CASE(3, ptr, nextTokPtr) \ + INVALID_LEAD_CASE(4, ptr, nextTokPtr) \ + case BT_NONXML: \ + case BT_MALFORM: \ + case BT_TRAIL: \ + *(nextTokPtr) = (ptr); \ + return XML_TOK_INVALID; + +#define CHECK_NAME_CASE(n, enc, ptr, end, nextTokPtr) \ + case BT_LEAD ## n: \ + if (end - ptr < n) \ + return XML_TOK_PARTIAL_CHAR; \ + if (!IS_NAME_CHAR(enc, ptr, n)) { \ + *nextTokPtr = ptr; \ + return XML_TOK_INVALID; \ + } \ + ptr += n; \ + break; + +#define CHECK_NAME_CASES(enc, ptr, end, nextTokPtr) \ + case BT_NONASCII: \ + if (!IS_NAME_CHAR_MINBPC(enc, ptr)) { \ + *nextTokPtr = ptr; \ + return XML_TOK_INVALID; \ + } \ + case BT_NMSTRT: \ + case BT_HEX: \ + case BT_DIGIT: \ + case BT_NAME: \ + case BT_MINUS: \ + ptr += MINBPC(enc); \ + break; \ + CHECK_NAME_CASE(2, enc, ptr, end, nextTokPtr) \ + CHECK_NAME_CASE(3, enc, ptr, end, nextTokPtr) \ + CHECK_NAME_CASE(4, enc, ptr, end, nextTokPtr) + +#define CHECK_NMSTRT_CASE(n, enc, ptr, end, nextTokPtr) \ + case BT_LEAD ## n: \ + if (end - ptr < n) \ + return XML_TOK_PARTIAL_CHAR; \ + if (!IS_NMSTRT_CHAR(enc, ptr, n)) { \ + *nextTokPtr = ptr; \ + return XML_TOK_INVALID; \ + } \ + ptr += n; \ + break; + +#define CHECK_NMSTRT_CASES(enc, ptr, end, nextTokPtr) \ + case BT_NONASCII: \ + if (!IS_NMSTRT_CHAR_MINBPC(enc, ptr)) { \ + *nextTokPtr = ptr; \ + return XML_TOK_INVALID; \ + } \ + case BT_NMSTRT: \ + case BT_HEX: \ + ptr += MINBPC(enc); \ + break; \ + CHECK_NMSTRT_CASE(2, enc, ptr, end, nextTokPtr) \ + CHECK_NMSTRT_CASE(3, enc, ptr, end, nextTokPtr) \ + CHECK_NMSTRT_CASE(4, enc, ptr, end, nextTokPtr) + +#ifndef PREFIX +#define PREFIX(ident) ident +#endif + +/* ptr points to character following " */ + switch (BYTE_TYPE(enc, ptr + MINBPC(enc))) { + case BT_S: case BT_CR: case BT_LF: case BT_PERCNT: + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + /* fall through */ + case BT_S: case BT_CR: case BT_LF: + *nextTokPtr = ptr; + return XML_TOK_DECL_OPEN; + case BT_NMSTRT: + case BT_HEX: + ptr += MINBPC(enc); + break; + default: + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + } + return XML_TOK_PARTIAL; +} + +static int PTRCALL +PREFIX(checkPiTarget)(const ENCODING *enc, const char *ptr, + const char *end, int *tokPtr) +{ + int upper = 0; + *tokPtr = XML_TOK_PI; + if (end - ptr != MINBPC(enc)*3) + return 1; + switch (BYTE_TO_ASCII(enc, ptr)) { + case ASCII_x: + break; + case ASCII_X: + upper = 1; + break; + default: + return 1; + } + ptr += MINBPC(enc); + switch (BYTE_TO_ASCII(enc, ptr)) { + case ASCII_m: + break; + case ASCII_M: + upper = 1; + break; + default: + return 1; + } + ptr += MINBPC(enc); + switch (BYTE_TO_ASCII(enc, ptr)) { + case ASCII_l: + break; + case ASCII_L: + upper = 1; + break; + default: + return 1; + } + if (upper) + return 0; + *tokPtr = XML_TOK_XML_DECL; + return 1; +} + +/* ptr points to character following " 1) { + size_t n = end - ptr; + if (n & (MINBPC(enc) - 1)) { + n &= ~(MINBPC(enc) - 1); + if (n == 0) + return XML_TOK_PARTIAL; + end = ptr + n; + } + } + switch (BYTE_TYPE(enc, ptr)) { + case BT_RSQB: + ptr += MINBPC(enc); + if (ptr == end) + return XML_TOK_PARTIAL; + if (!CHAR_MATCHES(enc, ptr, ASCII_RSQB)) + break; + ptr += MINBPC(enc); + if (ptr == end) + return XML_TOK_PARTIAL; + if (!CHAR_MATCHES(enc, ptr, ASCII_GT)) { + ptr -= MINBPC(enc); + break; + } + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_CDATA_SECT_CLOSE; + case BT_CR: + ptr += MINBPC(enc); + if (ptr == end) + return XML_TOK_PARTIAL; + if (BYTE_TYPE(enc, ptr) == BT_LF) + ptr += MINBPC(enc); + *nextTokPtr = ptr; + return XML_TOK_DATA_NEWLINE; + case BT_LF: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_DATA_NEWLINE; + INVALID_CASES(ptr, nextTokPtr) + default: + ptr += MINBPC(enc); + break; + } + while (ptr != end) { + switch (BYTE_TYPE(enc, ptr)) { +#define LEAD_CASE(n) \ + case BT_LEAD ## n: \ + if (end - ptr < n || IS_INVALID_CHAR(enc, ptr, n)) { \ + *nextTokPtr = ptr; \ + return XML_TOK_DATA_CHARS; \ + } \ + ptr += n; \ + break; + LEAD_CASE(2) LEAD_CASE(3) LEAD_CASE(4) +#undef LEAD_CASE + case BT_NONXML: + case BT_MALFORM: + case BT_TRAIL: + case BT_CR: + case BT_LF: + case BT_RSQB: + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + default: + ptr += MINBPC(enc); + break; + } + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; +} + +/* ptr points to character following " 1) { + size_t n = end - ptr; + if (n & (MINBPC(enc) - 1)) { + n &= ~(MINBPC(enc) - 1); + if (n == 0) + return XML_TOK_PARTIAL; + end = ptr + n; + } + } + switch (BYTE_TYPE(enc, ptr)) { + case BT_LT: + return PREFIX(scanLt)(enc, ptr + MINBPC(enc), end, nextTokPtr); + case BT_AMP: + return PREFIX(scanRef)(enc, ptr + MINBPC(enc), end, nextTokPtr); + case BT_CR: + ptr += MINBPC(enc); + if (ptr == end) + return XML_TOK_TRAILING_CR; + if (BYTE_TYPE(enc, ptr) == BT_LF) + ptr += MINBPC(enc); + *nextTokPtr = ptr; + return XML_TOK_DATA_NEWLINE; + case BT_LF: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_DATA_NEWLINE; + case BT_RSQB: + ptr += MINBPC(enc); + if (ptr == end) + return XML_TOK_TRAILING_RSQB; + if (!CHAR_MATCHES(enc, ptr, ASCII_RSQB)) + break; + ptr += MINBPC(enc); + if (ptr == end) + return XML_TOK_TRAILING_RSQB; + if (!CHAR_MATCHES(enc, ptr, ASCII_GT)) { + ptr -= MINBPC(enc); + break; + } + *nextTokPtr = ptr; + return XML_TOK_INVALID; + INVALID_CASES(ptr, nextTokPtr) + default: + ptr += MINBPC(enc); + break; + } + while (ptr != end) { + switch (BYTE_TYPE(enc, ptr)) { +#define LEAD_CASE(n) \ + case BT_LEAD ## n: \ + if (end - ptr < n || IS_INVALID_CHAR(enc, ptr, n)) { \ + *nextTokPtr = ptr; \ + return XML_TOK_DATA_CHARS; \ + } \ + ptr += n; \ + break; + LEAD_CASE(2) LEAD_CASE(3) LEAD_CASE(4) +#undef LEAD_CASE + case BT_RSQB: + if (ptr + MINBPC(enc) != end) { + if (!CHAR_MATCHES(enc, ptr + MINBPC(enc), ASCII_RSQB)) { + ptr += MINBPC(enc); + break; + } + if (ptr + 2*MINBPC(enc) != end) { + if (!CHAR_MATCHES(enc, ptr + 2*MINBPC(enc), ASCII_GT)) { + ptr += MINBPC(enc); + break; + } + *nextTokPtr = ptr + 2*MINBPC(enc); + return XML_TOK_INVALID; + } + } + /* fall through */ + case BT_AMP: + case BT_LT: + case BT_NONXML: + case BT_MALFORM: + case BT_TRAIL: + case BT_CR: + case BT_LF: + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + default: + ptr += MINBPC(enc); + break; + } + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; +} + +/* ptr points to character following "%" */ + +static int PTRCALL +PREFIX(scanPercent)(const ENCODING *enc, const char *ptr, const char *end, + const char **nextTokPtr) +{ + if (ptr == end) + return -XML_TOK_PERCENT; + switch (BYTE_TYPE(enc, ptr)) { + CHECK_NMSTRT_CASES(enc, ptr, end, nextTokPtr) + case BT_S: case BT_LF: case BT_CR: case BT_PERCNT: + *nextTokPtr = ptr; + return XML_TOK_PERCENT; + default: + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + while (ptr != end) { + switch (BYTE_TYPE(enc, ptr)) { + CHECK_NAME_CASES(enc, ptr, end, nextTokPtr) + case BT_SEMI: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_PARAM_ENTITY_REF; + default: + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + } + return XML_TOK_PARTIAL; +} + +static int PTRCALL +PREFIX(scanPoundName)(const ENCODING *enc, const char *ptr, const char *end, + const char **nextTokPtr) +{ + if (ptr == end) + return XML_TOK_PARTIAL; + switch (BYTE_TYPE(enc, ptr)) { + CHECK_NMSTRT_CASES(enc, ptr, end, nextTokPtr) + default: + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + while (ptr != end) { + switch (BYTE_TYPE(enc, ptr)) { + CHECK_NAME_CASES(enc, ptr, end, nextTokPtr) + case BT_CR: case BT_LF: case BT_S: + case BT_RPAR: case BT_GT: case BT_PERCNT: case BT_VERBAR: + *nextTokPtr = ptr; + return XML_TOK_POUND_NAME; + default: + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + } + return -XML_TOK_POUND_NAME; +} + +static int PTRCALL +PREFIX(scanLit)(int open, const ENCODING *enc, + const char *ptr, const char *end, + const char **nextTokPtr) +{ + while (ptr != end) { + int t = BYTE_TYPE(enc, ptr); + switch (t) { + INVALID_CASES(ptr, nextTokPtr) + case BT_QUOT: + case BT_APOS: + ptr += MINBPC(enc); + if (t != open) + break; + if (ptr == end) + return -XML_TOK_LITERAL; + *nextTokPtr = ptr; + switch (BYTE_TYPE(enc, ptr)) { + case BT_S: case BT_CR: case BT_LF: + case BT_GT: case BT_PERCNT: case BT_LSQB: + return XML_TOK_LITERAL; + default: + return XML_TOK_INVALID; + } + default: + ptr += MINBPC(enc); + break; + } + } + return XML_TOK_PARTIAL; +} + +static int PTRCALL +PREFIX(prologTok)(const ENCODING *enc, const char *ptr, const char *end, + const char **nextTokPtr) +{ + int tok; + if (ptr == end) + return XML_TOK_NONE; + if (MINBPC(enc) > 1) { + size_t n = end - ptr; + if (n & (MINBPC(enc) - 1)) { + n &= ~(MINBPC(enc) - 1); + if (n == 0) + return XML_TOK_PARTIAL; + end = ptr + n; + } + } + switch (BYTE_TYPE(enc, ptr)) { + case BT_QUOT: + return PREFIX(scanLit)(BT_QUOT, enc, ptr + MINBPC(enc), end, nextTokPtr); + case BT_APOS: + return PREFIX(scanLit)(BT_APOS, enc, ptr + MINBPC(enc), end, nextTokPtr); + case BT_LT: + { + ptr += MINBPC(enc); + if (ptr == end) + return XML_TOK_PARTIAL; + switch (BYTE_TYPE(enc, ptr)) { + case BT_EXCL: + return PREFIX(scanDecl)(enc, ptr + MINBPC(enc), end, nextTokPtr); + case BT_QUEST: + return PREFIX(scanPi)(enc, ptr + MINBPC(enc), end, nextTokPtr); + case BT_NMSTRT: + case BT_HEX: + case BT_NONASCII: + case BT_LEAD2: + case BT_LEAD3: + case BT_LEAD4: + *nextTokPtr = ptr - MINBPC(enc); + return XML_TOK_INSTANCE_START; + } + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + case BT_CR: + if (ptr + MINBPC(enc) == end) { + *nextTokPtr = end; + /* indicate that this might be part of a CR/LF pair */ + return -XML_TOK_PROLOG_S; + } + /* fall through */ + case BT_S: case BT_LF: + for (;;) { + ptr += MINBPC(enc); + if (ptr == end) + break; + switch (BYTE_TYPE(enc, ptr)) { + case BT_S: case BT_LF: + break; + case BT_CR: + /* don't split CR/LF pair */ + if (ptr + MINBPC(enc) != end) + break; + /* fall through */ + default: + *nextTokPtr = ptr; + return XML_TOK_PROLOG_S; + } + } + *nextTokPtr = ptr; + return XML_TOK_PROLOG_S; + case BT_PERCNT: + return PREFIX(scanPercent)(enc, ptr + MINBPC(enc), end, nextTokPtr); + case BT_COMMA: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_COMMA; + case BT_LSQB: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_OPEN_BRACKET; + case BT_RSQB: + ptr += MINBPC(enc); + if (ptr == end) + return -XML_TOK_CLOSE_BRACKET; + if (CHAR_MATCHES(enc, ptr, ASCII_RSQB)) { + if (ptr + MINBPC(enc) == end) + return XML_TOK_PARTIAL; + if (CHAR_MATCHES(enc, ptr + MINBPC(enc), ASCII_GT)) { + *nextTokPtr = ptr + 2*MINBPC(enc); + return XML_TOK_COND_SECT_CLOSE; + } + } + *nextTokPtr = ptr; + return XML_TOK_CLOSE_BRACKET; + case BT_LPAR: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_OPEN_PAREN; + case BT_RPAR: + ptr += MINBPC(enc); + if (ptr == end) + return -XML_TOK_CLOSE_PAREN; + switch (BYTE_TYPE(enc, ptr)) { + case BT_AST: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_CLOSE_PAREN_ASTERISK; + case BT_QUEST: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_CLOSE_PAREN_QUESTION; + case BT_PLUS: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_CLOSE_PAREN_PLUS; + case BT_CR: case BT_LF: case BT_S: + case BT_GT: case BT_COMMA: case BT_VERBAR: + case BT_RPAR: + *nextTokPtr = ptr; + return XML_TOK_CLOSE_PAREN; + } + *nextTokPtr = ptr; + return XML_TOK_INVALID; + case BT_VERBAR: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_OR; + case BT_GT: + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_DECL_CLOSE; + case BT_NUM: + return PREFIX(scanPoundName)(enc, ptr + MINBPC(enc), end, nextTokPtr); +#define LEAD_CASE(n) \ + case BT_LEAD ## n: \ + if (end - ptr < n) \ + return XML_TOK_PARTIAL_CHAR; \ + if (IS_NMSTRT_CHAR(enc, ptr, n)) { \ + ptr += n; \ + tok = XML_TOK_NAME; \ + break; \ + } \ + if (IS_NAME_CHAR(enc, ptr, n)) { \ + ptr += n; \ + tok = XML_TOK_NMTOKEN; \ + break; \ + } \ + *nextTokPtr = ptr; \ + return XML_TOK_INVALID; + LEAD_CASE(2) LEAD_CASE(3) LEAD_CASE(4) +#undef LEAD_CASE + case BT_NMSTRT: + case BT_HEX: + tok = XML_TOK_NAME; + ptr += MINBPC(enc); + break; + case BT_DIGIT: + case BT_NAME: + case BT_MINUS: +#ifdef XML_NS + case BT_COLON: +#endif + tok = XML_TOK_NMTOKEN; + ptr += MINBPC(enc); + break; + case BT_NONASCII: + if (IS_NMSTRT_CHAR_MINBPC(enc, ptr)) { + ptr += MINBPC(enc); + tok = XML_TOK_NAME; + break; + } + if (IS_NAME_CHAR_MINBPC(enc, ptr)) { + ptr += MINBPC(enc); + tok = XML_TOK_NMTOKEN; + break; + } + /* fall through */ + default: + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + while (ptr != end) { + switch (BYTE_TYPE(enc, ptr)) { + CHECK_NAME_CASES(enc, ptr, end, nextTokPtr) + case BT_GT: case BT_RPAR: case BT_COMMA: + case BT_VERBAR: case BT_LSQB: case BT_PERCNT: + case BT_S: case BT_CR: case BT_LF: + *nextTokPtr = ptr; + return tok; +#ifdef XML_NS + case BT_COLON: + ptr += MINBPC(enc); + switch (tok) { + case XML_TOK_NAME: + if (ptr == end) + return XML_TOK_PARTIAL; + tok = XML_TOK_PREFIXED_NAME; + switch (BYTE_TYPE(enc, ptr)) { + CHECK_NAME_CASES(enc, ptr, end, nextTokPtr) + default: + tok = XML_TOK_NMTOKEN; + break; + } + break; + case XML_TOK_PREFIXED_NAME: + tok = XML_TOK_NMTOKEN; + break; + } + break; +#endif + case BT_PLUS: + if (tok == XML_TOK_NMTOKEN) { + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_NAME_PLUS; + case BT_AST: + if (tok == XML_TOK_NMTOKEN) { + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_NAME_ASTERISK; + case BT_QUEST: + if (tok == XML_TOK_NMTOKEN) { + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_NAME_QUESTION; + default: + *nextTokPtr = ptr; + return XML_TOK_INVALID; + } + } + return -tok; +} + +static int PTRCALL +PREFIX(attributeValueTok)(const ENCODING *enc, const char *ptr, + const char *end, const char **nextTokPtr) +{ + const char *start; + if (ptr == end) + return XML_TOK_NONE; + start = ptr; + while (ptr != end) { + switch (BYTE_TYPE(enc, ptr)) { +#define LEAD_CASE(n) \ + case BT_LEAD ## n: ptr += n; break; + LEAD_CASE(2) LEAD_CASE(3) LEAD_CASE(4) +#undef LEAD_CASE + case BT_AMP: + if (ptr == start) + return PREFIX(scanRef)(enc, ptr + MINBPC(enc), end, nextTokPtr); + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + case BT_LT: + /* this is for inside entity references */ + *nextTokPtr = ptr; + return XML_TOK_INVALID; + case BT_LF: + if (ptr == start) { + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_DATA_NEWLINE; + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + case BT_CR: + if (ptr == start) { + ptr += MINBPC(enc); + if (ptr == end) + return XML_TOK_TRAILING_CR; + if (BYTE_TYPE(enc, ptr) == BT_LF) + ptr += MINBPC(enc); + *nextTokPtr = ptr; + return XML_TOK_DATA_NEWLINE; + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + case BT_S: + if (ptr == start) { + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_ATTRIBUTE_VALUE_S; + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + default: + ptr += MINBPC(enc); + break; + } + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; +} + +static int PTRCALL +PREFIX(entityValueTok)(const ENCODING *enc, const char *ptr, + const char *end, const char **nextTokPtr) +{ + const char *start; + if (ptr == end) + return XML_TOK_NONE; + start = ptr; + while (ptr != end) { + switch (BYTE_TYPE(enc, ptr)) { +#define LEAD_CASE(n) \ + case BT_LEAD ## n: ptr += n; break; + LEAD_CASE(2) LEAD_CASE(3) LEAD_CASE(4) +#undef LEAD_CASE + case BT_AMP: + if (ptr == start) + return PREFIX(scanRef)(enc, ptr + MINBPC(enc), end, nextTokPtr); + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + case BT_PERCNT: + if (ptr == start) { + int tok = PREFIX(scanPercent)(enc, ptr + MINBPC(enc), + end, nextTokPtr); + return (tok == XML_TOK_PERCENT) ? XML_TOK_INVALID : tok; + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + case BT_LF: + if (ptr == start) { + *nextTokPtr = ptr + MINBPC(enc); + return XML_TOK_DATA_NEWLINE; + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + case BT_CR: + if (ptr == start) { + ptr += MINBPC(enc); + if (ptr == end) + return XML_TOK_TRAILING_CR; + if (BYTE_TYPE(enc, ptr) == BT_LF) + ptr += MINBPC(enc); + *nextTokPtr = ptr; + return XML_TOK_DATA_NEWLINE; + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; + default: + ptr += MINBPC(enc); + break; + } + } + *nextTokPtr = ptr; + return XML_TOK_DATA_CHARS; +} + +#ifdef XML_DTD + +static int PTRCALL +PREFIX(ignoreSectionTok)(const ENCODING *enc, const char *ptr, + const char *end, const char **nextTokPtr) +{ + int level = 0; + if (MINBPC(enc) > 1) { + size_t n = end - ptr; + if (n & (MINBPC(enc) - 1)) { + n &= ~(MINBPC(enc) - 1); + end = ptr + n; + } + } + while (ptr != end) { + switch (BYTE_TYPE(enc, ptr)) { + INVALID_CASES(ptr, nextTokPtr) + case BT_LT: + if ((ptr += MINBPC(enc)) == end) + return XML_TOK_PARTIAL; + if (CHAR_MATCHES(enc, ptr, ASCII_EXCL)) { + if ((ptr += MINBPC(enc)) == end) + return XML_TOK_PARTIAL; + if (CHAR_MATCHES(enc, ptr, ASCII_LSQB)) { + ++level; + ptr += MINBPC(enc); + } + } + break; + case BT_RSQB: + if ((ptr += MINBPC(enc)) == end) + return XML_TOK_PARTIAL; + if (CHAR_MATCHES(enc, ptr, ASCII_RSQB)) { + if ((ptr += MINBPC(enc)) == end) + return XML_TOK_PARTIAL; + if (CHAR_MATCHES(enc, ptr, ASCII_GT)) { + ptr += MINBPC(enc); + if (level == 0) { + *nextTokPtr = ptr; + return XML_TOK_IGNORE_SECT; + } + --level; + } + } + break; + default: + ptr += MINBPC(enc); + break; + } + } + return XML_TOK_PARTIAL; +} + +#endif /* XML_DTD */ + +static int PTRCALL +PREFIX(isPublicId)(const ENCODING *enc, const char *ptr, const char *end, + const char **badPtr) +{ + ptr += MINBPC(enc); + end -= MINBPC(enc); + for (; ptr != end; ptr += MINBPC(enc)) { + switch (BYTE_TYPE(enc, ptr)) { + case BT_DIGIT: + case BT_HEX: + case BT_MINUS: + case BT_APOS: + case BT_LPAR: + case BT_RPAR: + case BT_PLUS: + case BT_COMMA: + case BT_SOL: + case BT_EQUALS: + case BT_QUEST: + case BT_CR: + case BT_LF: + case BT_SEMI: + case BT_EXCL: + case BT_AST: + case BT_PERCNT: + case BT_NUM: +#ifdef XML_NS + case BT_COLON: +#endif + break; + case BT_S: + if (CHAR_MATCHES(enc, ptr, ASCII_TAB)) { + *badPtr = ptr; + return 0; + } + break; + case BT_NAME: + case BT_NMSTRT: + if (!(BYTE_TO_ASCII(enc, ptr) & ~0x7f)) + break; + default: + switch (BYTE_TO_ASCII(enc, ptr)) { + case 0x24: /* $ */ + case 0x40: /* @ */ + break; + default: + *badPtr = ptr; + return 0; + } + break; + } + } + return 1; +} + +/* This must only be called for a well-formed start-tag or empty + element tag. Returns the number of attributes. Pointers to the + first attsMax attributes are stored in atts. +*/ + +static int PTRCALL +PREFIX(getAtts)(const ENCODING *enc, const char *ptr, + int attsMax, ATTRIBUTE *atts) +{ + enum { other, inName, inValue } state = inName; + int nAtts = 0; + int open = 0; /* defined when state == inValue; + initialization just to shut up compilers */ + + for (ptr += MINBPC(enc);; ptr += MINBPC(enc)) { + switch (BYTE_TYPE(enc, ptr)) { +#define START_NAME \ + if (state == other) { \ + if (nAtts < attsMax) { \ + atts[nAtts].name = ptr; \ + atts[nAtts].normalized = 1; \ + } \ + state = inName; \ + } +#define LEAD_CASE(n) \ + case BT_LEAD ## n: START_NAME ptr += (n - MINBPC(enc)); break; + LEAD_CASE(2) LEAD_CASE(3) LEAD_CASE(4) +#undef LEAD_CASE + case BT_NONASCII: + case BT_NMSTRT: + case BT_HEX: + START_NAME + break; +#undef START_NAME + case BT_QUOT: + if (state != inValue) { + if (nAtts < attsMax) + atts[nAtts].valuePtr = ptr + MINBPC(enc); + state = inValue; + open = BT_QUOT; + } + else if (open == BT_QUOT) { + state = other; + if (nAtts < attsMax) + atts[nAtts].valueEnd = ptr; + nAtts++; + } + break; + case BT_APOS: + if (state != inValue) { + if (nAtts < attsMax) + atts[nAtts].valuePtr = ptr + MINBPC(enc); + state = inValue; + open = BT_APOS; + } + else if (open == BT_APOS) { + state = other; + if (nAtts < attsMax) + atts[nAtts].valueEnd = ptr; + nAtts++; + } + break; + case BT_AMP: + if (nAtts < attsMax) + atts[nAtts].normalized = 0; + break; + case BT_S: + if (state == inName) + state = other; + else if (state == inValue + && nAtts < attsMax + && atts[nAtts].normalized + && (ptr == atts[nAtts].valuePtr + || BYTE_TO_ASCII(enc, ptr) != ASCII_SPACE + || BYTE_TO_ASCII(enc, ptr + MINBPC(enc)) == ASCII_SPACE + || BYTE_TYPE(enc, ptr + MINBPC(enc)) == open)) + atts[nAtts].normalized = 0; + break; + case BT_CR: case BT_LF: + /* This case ensures that the first attribute name is counted + Apart from that we could just change state on the quote. */ + if (state == inName) + state = other; + else if (state == inValue && nAtts < attsMax) + atts[nAtts].normalized = 0; + break; + case BT_GT: + case BT_SOL: + if (state != inValue) + return nAtts; + break; + default: + break; + } + } + /* not reached */ +} + +static int PTRFASTCALL +PREFIX(charRefNumber)(const ENCODING *enc, const char *ptr) +{ + int result = 0; + /* skip &# */ + ptr += 2*MINBPC(enc); + if (CHAR_MATCHES(enc, ptr, ASCII_x)) { + for (ptr += MINBPC(enc); + !CHAR_MATCHES(enc, ptr, ASCII_SEMI); + ptr += MINBPC(enc)) { + int c = BYTE_TO_ASCII(enc, ptr); + switch (c) { + case ASCII_0: case ASCII_1: case ASCII_2: case ASCII_3: case ASCII_4: + case ASCII_5: case ASCII_6: case ASCII_7: case ASCII_8: case ASCII_9: + result <<= 4; + result |= (c - ASCII_0); + break; + case ASCII_A: case ASCII_B: case ASCII_C: + case ASCII_D: case ASCII_E: case ASCII_F: + result <<= 4; + result += 10 + (c - ASCII_A); + break; + case ASCII_a: case ASCII_b: case ASCII_c: + case ASCII_d: case ASCII_e: case ASCII_f: + result <<= 4; + result += 10 + (c - ASCII_a); + break; + } + if (result >= 0x110000) + return -1; + } + } + else { + for (; !CHAR_MATCHES(enc, ptr, ASCII_SEMI); ptr += MINBPC(enc)) { + int c = BYTE_TO_ASCII(enc, ptr); + result *= 10; + result += (c - ASCII_0); + if (result >= 0x110000) + return -1; + } + } + return checkCharRefNumber(result); +} + +static int PTRCALL +PREFIX(predefinedEntityName)(const ENCODING *enc, const char *ptr, + const char *end) +{ + switch ((end - ptr)/MINBPC(enc)) { + case 2: + if (CHAR_MATCHES(enc, ptr + MINBPC(enc), ASCII_t)) { + switch (BYTE_TO_ASCII(enc, ptr)) { + case ASCII_l: + return ASCII_LT; + case ASCII_g: + return ASCII_GT; + } + } + break; + case 3: + if (CHAR_MATCHES(enc, ptr, ASCII_a)) { + ptr += MINBPC(enc); + if (CHAR_MATCHES(enc, ptr, ASCII_m)) { + ptr += MINBPC(enc); + if (CHAR_MATCHES(enc, ptr, ASCII_p)) + return ASCII_AMP; + } + } + break; + case 4: + switch (BYTE_TO_ASCII(enc, ptr)) { + case ASCII_q: + ptr += MINBPC(enc); + if (CHAR_MATCHES(enc, ptr, ASCII_u)) { + ptr += MINBPC(enc); + if (CHAR_MATCHES(enc, ptr, ASCII_o)) { + ptr += MINBPC(enc); + if (CHAR_MATCHES(enc, ptr, ASCII_t)) + return ASCII_QUOT; + } + } + break; + case ASCII_a: + ptr += MINBPC(enc); + if (CHAR_MATCHES(enc, ptr, ASCII_p)) { + ptr += MINBPC(enc); + if (CHAR_MATCHES(enc, ptr, ASCII_o)) { + ptr += MINBPC(enc); + if (CHAR_MATCHES(enc, ptr, ASCII_s)) + return ASCII_APOS; + } + } + break; + } + } + return 0; +} + +static int PTRCALL +PREFIX(sameName)(const ENCODING *enc, const char *ptr1, const char *ptr2) +{ + for (;;) { + switch (BYTE_TYPE(enc, ptr1)) { +#define LEAD_CASE(n) \ + case BT_LEAD ## n: \ + if (*ptr1++ != *ptr2++) \ + return 0; + LEAD_CASE(4) LEAD_CASE(3) LEAD_CASE(2) +#undef LEAD_CASE + /* fall through */ + if (*ptr1++ != *ptr2++) + return 0; + break; + case BT_NONASCII: + case BT_NMSTRT: +#ifdef XML_NS + case BT_COLON: +#endif + case BT_HEX: + case BT_DIGIT: + case BT_NAME: + case BT_MINUS: + if (*ptr2++ != *ptr1++) + return 0; + if (MINBPC(enc) > 1) { + if (*ptr2++ != *ptr1++) + return 0; + if (MINBPC(enc) > 2) { + if (*ptr2++ != *ptr1++) + return 0; + if (MINBPC(enc) > 3) { + if (*ptr2++ != *ptr1++) + return 0; + } + } + } + break; + default: + if (MINBPC(enc) == 1 && *ptr1 == *ptr2) + return 1; + switch (BYTE_TYPE(enc, ptr2)) { + case BT_LEAD2: + case BT_LEAD3: + case BT_LEAD4: + case BT_NONASCII: + case BT_NMSTRT: +#ifdef XML_NS + case BT_COLON: +#endif + case BT_HEX: + case BT_DIGIT: + case BT_NAME: + case BT_MINUS: + return 0; + default: + return 1; + } + } + } + /* not reached */ +} + +static int PTRCALL +PREFIX(nameMatchesAscii)(const ENCODING *enc, const char *ptr1, + const char *end1, const char *ptr2) +{ + for (; *ptr2; ptr1 += MINBPC(enc), ptr2++) { + if (ptr1 == end1) + return 0; + if (!CHAR_MATCHES(enc, ptr1, *ptr2)) + return 0; + } + return ptr1 == end1; +} + +static int PTRFASTCALL +PREFIX(nameLength)(const ENCODING *enc, const char *ptr) +{ + const char *start = ptr; + for (;;) { + switch (BYTE_TYPE(enc, ptr)) { +#define LEAD_CASE(n) \ + case BT_LEAD ## n: ptr += n; break; + LEAD_CASE(2) LEAD_CASE(3) LEAD_CASE(4) +#undef LEAD_CASE + case BT_NONASCII: + case BT_NMSTRT: +#ifdef XML_NS + case BT_COLON: +#endif + case BT_HEX: + case BT_DIGIT: + case BT_NAME: + case BT_MINUS: + ptr += MINBPC(enc); + break; + default: + return ptr - start; + } + } +} + +static const char * PTRFASTCALL +PREFIX(skipS)(const ENCODING *enc, const char *ptr) +{ + for (;;) { + switch (BYTE_TYPE(enc, ptr)) { + case BT_LF: + case BT_CR: + case BT_S: + ptr += MINBPC(enc); + break; + default: + return ptr; + } + } +} + +static void PTRCALL +PREFIX(updatePosition)(const ENCODING *enc, + const char *ptr, + const char *end, + POSITION *pos) +{ + while (ptr != end) { + switch (BYTE_TYPE(enc, ptr)) { +#define LEAD_CASE(n) \ + case BT_LEAD ## n: \ + ptr += n; \ + break; + LEAD_CASE(2) LEAD_CASE(3) LEAD_CASE(4) +#undef LEAD_CASE + case BT_LF: + pos->columnNumber = (unsigned)-1; + pos->lineNumber++; + ptr += MINBPC(enc); + break; + case BT_CR: + pos->lineNumber++; + ptr += MINBPC(enc); + if (ptr != end && BYTE_TYPE(enc, ptr) == BT_LF) + ptr += MINBPC(enc); + pos->columnNumber = (unsigned)-1; + break; + default: + ptr += MINBPC(enc); + break; + } + pos->columnNumber++; + } +} + +#undef DO_LEAD_CASE +#undef MULTIBYTE_CASES +#undef INVALID_CASES +#undef CHECK_NAME_CASE +#undef CHECK_NAME_CASES +#undef CHECK_NMSTRT_CASE +#undef CHECK_NMSTRT_CASES + diff --git a/expat/xmltok_impl.h b/expat/xmltok_impl.h new file mode 100644 index 00000000..da0ea60a --- /dev/null +++ b/expat/xmltok_impl.h @@ -0,0 +1,46 @@ +/* +Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd +See the file COPYING for copying permission. +*/ + +enum { + BT_NONXML, + BT_MALFORM, + BT_LT, + BT_AMP, + BT_RSQB, + BT_LEAD2, + BT_LEAD3, + BT_LEAD4, + BT_TRAIL, + BT_CR, + BT_LF, + BT_GT, + BT_QUOT, + BT_APOS, + BT_EQUALS, + BT_QUEST, + BT_EXCL, + BT_SOL, + BT_SEMI, + BT_NUM, + BT_LSQB, + BT_S, + BT_NMSTRT, + BT_COLON, + BT_HEX, + BT_DIGIT, + BT_NAME, + BT_MINUS, + BT_OTHER, /* known not to be a name or name start character */ + BT_NONASCII, /* might be a name or name start character */ + BT_PERCNT, + BT_LPAR, + BT_RPAR, + BT_AST, + BT_PLUS, + BT_COMMA, + BT_VERBAR +}; + +#include diff --git a/expat/xmltok_ns.c b/expat/xmltok_ns.c new file mode 100644 index 00000000..5610eb95 --- /dev/null +++ b/expat/xmltok_ns.c @@ -0,0 +1,106 @@ +const ENCODING * +NS(XmlGetUtf8InternalEncoding)(void) +{ + return &ns(internal_utf8_encoding).enc; +} + +const ENCODING * +NS(XmlGetUtf16InternalEncoding)(void) +{ +#if BYTEORDER == 1234 + return &ns(internal_little2_encoding).enc; +#elif BYTEORDER == 4321 + return &ns(internal_big2_encoding).enc; +#else + const short n = 1; + return (*(const char *)&n + ? &ns(internal_little2_encoding).enc + : &ns(internal_big2_encoding).enc); +#endif +} + +static const ENCODING *NS(encodings)[] = { + &ns(latin1_encoding).enc, + &ns(ascii_encoding).enc, + &ns(utf8_encoding).enc, + &ns(big2_encoding).enc, + &ns(big2_encoding).enc, + &ns(little2_encoding).enc, + &ns(utf8_encoding).enc /* NO_ENC */ +}; + +static int PTRCALL +NS(initScanProlog)(const ENCODING *enc, const char *ptr, const char *end, + const char **nextTokPtr) +{ + return initScan(NS(encodings), (const INIT_ENCODING *)enc, + XML_PROLOG_STATE, ptr, end, nextTokPtr); +} + +static int PTRCALL +NS(initScanContent)(const ENCODING *enc, const char *ptr, const char *end, + const char **nextTokPtr) +{ + return initScan(NS(encodings), (const INIT_ENCODING *)enc, + XML_CONTENT_STATE, ptr, end, nextTokPtr); +} + +int +NS(XmlInitEncoding)(INIT_ENCODING *p, const ENCODING **encPtr, + const char *name) +{ + int i = getEncodingIndex(name); + if (i == UNKNOWN_ENC) + return 0; + SET_INIT_ENC_INDEX(p, i); + p->initEnc.scanners[XML_PROLOG_STATE] = NS(initScanProlog); + p->initEnc.scanners[XML_CONTENT_STATE] = NS(initScanContent); + p->initEnc.updatePosition = initUpdatePosition; + p->encPtr = encPtr; + *encPtr = &(p->initEnc); + return 1; +} + +static const ENCODING * +NS(findEncoding)(const ENCODING *enc, const char *ptr, const char *end) +{ +#define ENCODING_MAX 128 + char buf[ENCODING_MAX]; + char *p = buf; + int i; + XmlUtf8Convert(enc, &ptr, end, &p, p + ENCODING_MAX - 1); + if (ptr != end) + return 0; + *p = 0; + if (streqci(buf, KW_UTF_16) && enc->minBytesPerChar == 2) + return enc; + i = getEncodingIndex(buf); + if (i == UNKNOWN_ENC) + return 0; + return NS(encodings)[i]; +} + +int +NS(XmlParseXmlDecl)(int isGeneralTextEntity, + const ENCODING *enc, + const char *ptr, + const char *end, + const char **badPtr, + const char **versionPtr, + const char **versionEndPtr, + const char **encodingName, + const ENCODING **encoding, + int *standalone) +{ + return doParseXmlDecl(NS(findEncoding), + isGeneralTextEntity, + enc, + ptr, + end, + badPtr, + versionPtr, + versionEndPtr, + encodingName, + encoding, + standalone); +} diff --git a/license-header b/license-header new file mode 100644 index 00000000..2435fbf1 --- /dev/null +++ b/license-header @@ -0,0 +1,20 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + diff --git a/man/.cvsignore b/man/.cvsignore new file mode 100644 index 00000000..dc9125a8 --- /dev/null +++ b/man/.cvsignore @@ -0,0 +1,2 @@ +Makefile Makefile.in +c2s.8 jabberd.8 resolver.8 router.8 s2s.8 sm.8 diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 00000000..15ea7a09 --- /dev/null +++ b/man/Makefile.am @@ -0,0 +1,22 @@ +man_MANS = jabberd.8 c2s.8 resolver.8 router.8 s2s.8 sm.8 +EXTRA_DIST = jabberd.8.in c2s.8.in resolver.8.in router.8.in s2s.8.in sm.8.in + +jabberd_bin = router resolver sm s2s c2s + +edit = sed \ + -e 's,@sysconfdir\@,$(sysconfdir),g' \ + -e 's,@VERSION\@,$(VERSION),g' + +$(man_MANS): + @echo "generating $@ from $@.in"; \ + edit='$(edit)'; \ + list='$(jabberd_bin)'; for p in $$list; do \ + bin=`echo "$$p" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + edit="$$edit -e s,@jabberd_$$p\_bin\\@,$$bin,g"; \ + done; \ + rm -f $@ $@.tmp; \ + eval "$$edit < $@.in > $@.tmp"; \ + mv $@.tmp $@ + +clean-local: + rm -f $(man_MANS) diff --git a/man/c2s.8.in b/man/c2s.8.in new file mode 100644 index 00000000..277e97d6 --- /dev/null +++ b/man/c2s.8.in @@ -0,0 +1,27 @@ +.TH @jabberd_c2s_bin@ 8 "28 August 2003" "@VERSION@" "jabberd project" +.SH NAME +@jabberd_c2s_bin@ \- client-to-server connector +.SH SYNOPSIS +.B @jabberd_c2s_bin@ +.I [-h] [-c config] [-D] +.SH DESCRIPTION +.BR @jabberd_c2s_bin@ +accepts incoming connections from clients, performs authentication and registration functions, and communicates with the session manager on their behalf. +.SH OPTIONS +.TP +.B \-h +Show summary of options. +.TP +.B \-c +Alternate configuration file to use. The compiled-in default is @sysconfdir@/c2s.xml. +.TP +.B \-D +Print debugging output. You should configure jabberd with the --enable-debug switch to enable this. +.SH SEE ALSO +.BR jabberd (8) +.BR @jabberd_router_bin@ (8) +.BR @jabberd_resolver_bin@ (8) +.BR @jabberd_s2s_bin@ (8) +.BR @jabberd_sm_bin@ (8) +.SH AUTHOR +Robert Norris diff --git a/man/jabberd.8.in b/man/jabberd.8.in new file mode 100644 index 00000000..1ba2f326 --- /dev/null +++ b/man/jabberd.8.in @@ -0,0 +1,27 @@ +.TH jabberd 8 "28 August 2003" "@VERSION@" "jabberd project" +.SH NAME +jabberd \- jabberd startup script +.SH SYNOPSIS +.B jabberd +.I [-h] [-c config] [-D] +.SH DESCRIPTION +.BR jabberd +is a script that runs the various components that make up the jabberd "server". +.SH OPTIONS +.TP +.B \-h +Show summary of options. +.TP +.B \-c +Alternate configuration file to use. The compiled-in default is @sysconfdir@/jabberd.cfg +.TP +.B \-D +Print debugging output. You should configure jabberd with the --enable-debug switch to enable this. +.SH SEE ALSO +.BR @jabberd_c2s_bin@ (8) +.BR @jabberd_router_bin@ (8) +.BR @jabberd_resolver_bin@ (8) +.BR @jabberd_s2s_bin@ (8) +.BR @jabberd_sm_bin@ (8) +.SH AUTHOR +Robert Norris diff --git a/man/resolver.8.in b/man/resolver.8.in new file mode 100644 index 00000000..7c8e0cd0 --- /dev/null +++ b/man/resolver.8.in @@ -0,0 +1,27 @@ +.TH @jabberd_resolver_bin@ 8 "28 August 2003" "@VERSION@" "jabberd project" +.SH NAME +@jabberd_resolver_bin@ \- DNS resolver +.SH SYNOPSIS +.B @jabberd_resolver_bin@ +.I [-h] [-c config] [-D] +.SH DESCRIPTION +.BR @jabberd_resolver_bin@ +is a helper component for s2s. It performs DNS lookups of external servers as required. +.SH OPTIONS +.TP +.B \-h +Show summary of options. +.TP +.B \-c +Alternate configuration file to use. The compiled-in default is @sysconfdir@/resolver.xml. +.TP +.B \-D +Print debugging output. You should configure jabberd with the --enable-debug switch to enable this. +.SH SEE ALSO +.BR jabberd (8) +.BR @jabberd_c2s_bin@ (8) +.BR @jabberd_router_bin@ (8) +.BR @jabberd_s2s_bin@ (8) +.BR @jabberd_sm_bin@ (8) +.SH AUTHOR +Robert Norris diff --git a/man/router.8.in b/man/router.8.in new file mode 100644 index 00000000..d5058dd6 --- /dev/null +++ b/man/router.8.in @@ -0,0 +1,27 @@ +.TH @jabberd_router_bin@ 8 "28 August 2003" "@VERSION@" "jabberd project" +.SH NAME +@jabberd_router_bin@ \- XML packet distributor +.SH SYNOPSIS +.B @jabberd_router_bin@ +.I [-h] [-c config] [-D] +.SH DESCRIPTION +.BR @jabberd_router_bin@ +is the core component of the jabberd system. It passes XML packets between the components, and manages the entry and exit of components onto the network. +.SH OPTIONS +.TP +.B \-h +Show summary of options. +.TP +.B \-c +Alternate configuration file to use. The compiled-in default is @sysconfdir@/router.xml. +.TP +.B \-D +Print debugging output. You should configure jabberd with the --enable-debug switch to enable this. +.SH SEE ALSO +.BR jabberd (8) +.BR @jabberd_c2s_bin@ (8) +.BR @jabberd_resolver_bin@ (8) +.BR @jabberd_s2s_bin@ (8) +.BR @jabberd_sm_bin@ (8) +.SH AUTHOR +Robert Norris diff --git a/man/s2s.8.in b/man/s2s.8.in new file mode 100644 index 00000000..fdca7201 --- /dev/null +++ b/man/s2s.8.in @@ -0,0 +1,27 @@ +.TH @jabberd_s2s_bin@ 8 "28 August 2003" "@VERSION@" "jabberd project" +.SH NAME +@jabberd_s2s_bin@ \- server-to-server connector +.SH SYNOPSIS +.B @jabberd_s2s_bin@ +.I [-h] [-c config] [-D] +.SH DESCRIPTION +.BR @jabberd_s2s_bin@ +manages connections between local components and other Jabber servers. +.SH OPTIONS +.TP +.B \-h +Show summary of options. +.TP +.B \-c +Alternate configuration file to use. The compiled-in default is @sysconfdir@/s2s.xml. +.TP +.B \-D +Print debugging output. You should configure jabberd with the --enable-debug switch to enable this. +.SH SEE ALSO +.BR jabberd (8) +.BR @jabberd_c2s_bin@ (8) +.BR @jabberd_router_bin@ (8) +.BR @jabberd_resolver_bin@ (8) +.BR @jabberd_sm_bin@ (8) +.SH AUTHOR +Robert Norris diff --git a/man/sm.8.in b/man/sm.8.in new file mode 100644 index 00000000..bb9d1127 --- /dev/null +++ b/man/sm.8.in @@ -0,0 +1,27 @@ +.TH @jabberd_sm_bin@ 8 "28 August 2003" "@VERSION@" "jabberd project" +.SH NAME +@jabberd_sm_bin@ \- Jabber IM session manager +.SH SYNOPSIS +.B @jabberd_sm_bin@ +.I [-h] [-c config] [-D] +.SH DESCRIPTION +.BR @jabberd_sm_bin@ +provides instant messaging services to Jabber clients. It performs all the essential instant messaging services like rosters, presence tracking, message distribution and subscriptions, plus more advanced features. +.SH OPTIONS +.TP +.B \-h +Show summary of options. +.TP +.B \-c +Alternate configuration file to use. The compiled-in default is @sysconfdir@/sm.xml. +.TP +.B \-D +Print debugging output. You should configure jabberd with the --enable-debug switch to enable this. +.SH SEE ALSO +.BR jabberd (8) +.BR @jabberd_c2s_bin@ (8) +.BR @jabberd_router_bin@ (8) +.BR @jabberd_resolver_bin@ (8) +.BR @jabberd_s2s_bin@ (8) +.SH AUTHOR +Robert Norris diff --git a/man/storage.8.in b/man/storage.8.in new file mode 100644 index 00000000..dee838b1 --- /dev/null +++ b/man/storage.8.in @@ -0,0 +1,28 @@ +.TH @jabberd_storage_bin@ 8 "27 April 2004" "@VERSION@" "jabberd project" +.SH NAME +@jabberd_storage_bin@ \- Jabber storage manager +.SH SYNOPSIS +.B @jabberd_storage_bin@ +.I [-h] [-c config] [-D] +.SH DESCRIPTION +.BR @jabberd_storage_bin@ +provides storage and authentication services to the other components in the jabberd system. +.SH OPTIONS +.TP +.B \-h +Show summary of options. +.TP +.B \-c +Alternate configuration file to use. The compiled-in default is @sysconfdir@/storage.xml. +.TP +.B \-D +Print debugging output. You should configure jabberd with the --enable-debug switch to enable this. +.SH SEE ALSO +.BR jabberd (8) +.BR @jabberd_c2s_bin@ (8) +.BR @jabberd_router_bin@ (8) +.BR @jabberd_resolver_bin@ (8) +.BR @jabberd_s2s_bin@ (8) +.BR @jabberd_sm_bin@ (8) +.SH AUTHOR +Robert Norris diff --git a/mio/.cvsignore b/mio/.cvsignore new file mode 100644 index 00000000..6e5ca7ed --- /dev/null +++ b/mio/.cvsignore @@ -0,0 +1,6 @@ +Makefile +Makefile.in +.deps +.libs +*.lo +*.la diff --git a/mio/Makefile.am b/mio/Makefile.am new file mode 100644 index 00000000..6ba87ae1 --- /dev/null +++ b/mio/Makefile.am @@ -0,0 +1,6 @@ +noinst_LTLIBRARIES = libmio.la + +noinst_HEADERS = mio.h mio_poll.h mio_select.h + +libmio_la_SOURCES = mio.c +libmio_la_LIBADD = @LDFLAGS@ diff --git a/mio/mio.c b/mio/mio.c new file mode 100644 index 00000000..8c08e550 --- /dev/null +++ b/mio/mio.c @@ -0,0 +1,449 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* + MIO -- Managed Input/Output + --------------------------- +*/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "mio.h" + +#include "util/inaddr.h" + +#ifdef MIO_POLL +#include "mio_poll.h" +#endif +#ifdef MIO_SELECT +#include "mio_select.h" +#endif + +/** our internal wrapper around a fd */ +typedef enum { + type_CLOSED = 0x00, + type_NORMAL = 0x01, + type_LISTEN = 0x02, + type_CONNECT = 0x10, + type_CONNECT_READ = 0x11, + type_CONNECT_WRITE = 0x12 +} mio_type_t; +struct mio_fd_st +{ + mio_type_t type; + /* app event handler and data */ + mio_handler_t app; + void *arg; +}; + +/** now define our master data type */ +struct mio_st +{ + struct mio_fd_st *fds; + int maxfd; + int highfd; + MIO_VARS +}; + +/* lazy factor */ +#define FD(m,f) m->fds[f] +#define ACT(m,f,a,d) (*(FD(m,f).app))(m,a,f,d,FD(m,f).arg) + +/* temp debug outputter */ +#define ZONE __LINE__ +#ifndef MIO_DEBUG +#define MIO_DEBUG 0 +#endif +#define mio_debug if(MIO_DEBUG) _mio_debug +void _mio_debug(int line, const char *msgfmt, ...) +{ + va_list ap; + va_start(ap,msgfmt); + fprintf(stderr,"mio.c#%d: ",line); + vfprintf(stderr,msgfmt,ap); + fprintf(stderr,"\n"); +} + +MIO_FUNCS + +/** internal close function */ +void mio_close(mio_t m, int fd) +{ + mio_debug(ZONE,"actually closing fd #%d",fd); + + /* take out of poll sets */ + MIO_REMOVE_FD(m, fd); + + /* let the app know, it must process any waiting write data it has and free it's arg */ + ACT(m, fd, action_CLOSE, NULL); + + /* close the socket, and reset all memory */ + close(fd); + memset(&FD(m,fd), 0, sizeof(struct mio_fd_st)); +} + +/** internally accept an incoming connection from a listen sock */ +void _mio_accept(mio_t m, int fd) +{ + struct sockaddr_storage serv_addr; + socklen_t addrlen = (socklen_t) sizeof(serv_addr); + int newfd, dupfd; + char ip[INET6_ADDRSTRLEN]; + + mio_debug(ZONE, "accepting on fd #%d", fd); + + /* pull a socket off the accept queue and check */ + newfd = accept(fd, (struct sockaddr*)&serv_addr, &addrlen); + if(newfd <= 0) return; + if(addrlen <= 0) { + close(newfd); + return; + } + + j_inet_ntop(&serv_addr, ip, sizeof(ip)); + mio_debug(ZONE, "new socket accepted fd #%d, %s:%d", newfd, ip, j_inet_getport(&serv_addr)); + + /* set up the entry for this new socket */ + if(mio_fd(m, newfd, FD(m,fd).app, FD(m,fd).arg) < 0) + { + /* too high, try and get a lower fd */ + dupfd = dup(newfd); + close(newfd); + + if(dupfd < 0 || mio_fd(m, dupfd, FD(m,fd).app, FD(m,fd).arg) < 0) { + mio_debug(ZONE,"failed to add fd"); + if(dupfd >= 0) close(dupfd); + + return; + } + + newfd = dupfd; + } + + /* tell the app about the new socket, if they reject it clean up */ + if (ACT(m, newfd, action_ACCEPT, ip)) + { + mio_debug(ZONE, "accept was rejected for %s:%d", ip, newfd); + MIO_REMOVE_FD(m, newfd); + + /* close the socket, and reset all memory */ + close(newfd); + memset(&FD(m, newfd), 0, sizeof(struct mio_fd_st)); + } + + return; +} + +/** internally change a connecting socket to a normal one */ +void _mio_connect(mio_t m, int fd) +{ + mio_type_t type = FD(m,fd).type; + + mio_debug(ZONE, "connect processing for fd #%d", fd); + + /* reset type and clear the "write" event that flags connect() is done */ + FD(m,fd).type = type_NORMAL; + MIO_UNSET_WRITE(m,fd); + + /* if the app had asked to do anything in the meantime, do those now */ + if(type & type_CONNECT_READ) mio_read(m,fd); + if(type & type_CONNECT_WRITE) mio_write(m,fd); +} + +/** add and set up this fd to this mio */ +int mio_fd(mio_t m, int fd, mio_handler_t app, void *arg) +{ + int flags; + + mio_debug(ZONE, "adding fd #%d", fd); + + if(fd >= m->maxfd) + { + mio_debug(ZONE,"fd to high"); + return -1; + } + + /* ok to process this one, welcome to the family */ + FD(m,fd).type = type_NORMAL; + FD(m,fd).app = app; + FD(m,fd).arg = arg; + MIO_INIT_FD(m, fd); + + /* set the socket to non-blocking */ +#if defined(HAVE_FCNTL) + flags = fcntl(fd, F_GETFL); + flags |= O_NONBLOCK; + fcntl(fd, F_SETFL, flags); +#elif defined(HAVE_IOCTL) + flags = 1; + ioctl(fd, FIONBIO, &flags); +#endif + + /* track highest used */ + if(fd > m->highfd) m->highfd = fd; + + return fd; +} + +/** reset app stuff for this fd */ +void mio_app(mio_t m, int fd, mio_handler_t app, void *arg) +{ + FD(m,fd).app = app; + FD(m,fd).arg = arg; +} + +/** main select loop runner */ +void mio_run(mio_t m, int timeout) +{ + int retval, fd; + + mio_debug(ZONE, "mio running for %d", timeout); + + /* wait for a socket event */ + retval = MIO_CHECK(m, timeout); + + /* nothing to do */ + if(retval == 0) return; + + /* an error */ + if(retval < 0) + { + mio_debug(ZONE, "MIO_CHECK returned an error (%d)", MIO_ERROR(m)); + + return; + } + + mio_debug(ZONE,"mio working: %d",retval); + + /* loop through the sockets, check for stuff to do */ + for(fd = 0; fd <= m->highfd; fd++) + { + /* skip dead slots */ + if(FD(m,fd).type == type_CLOSED) continue; + + /* new conns on a listen socket */ + if(FD(m,fd).type == type_LISTEN && MIO_CAN_READ(m, fd)) + { + _mio_accept(m, fd); + continue; + } + + /* check for connecting sockets */ + if(FD(m,fd).type & type_CONNECT && (MIO_CAN_READ(m, fd) || MIO_CAN_WRITE(m, fd))) + { + _mio_connect(m, fd); + continue; + } + + /* read from ready sockets */ + if(FD(m,fd).type == type_NORMAL && MIO_CAN_READ(m, fd)) + { + /* if they don't want to read any more right now */ + if(ACT(m, fd, action_READ, NULL) == 0) + MIO_UNSET_READ(m, fd); + } + + /* write to ready sockets */ + if(FD(m,fd).type == type_NORMAL && MIO_CAN_WRITE(m, fd)) + { + /* don't wait for writeability if nothing to write anymore */ + if(ACT(m, fd, action_WRITE, NULL) == 0) + MIO_UNSET_WRITE(m, fd); + } + } +} + +/** eve */ +mio_t mio_new(int maxfd) +{ + mio_t m; + + /* allocate and zero out main memory */ + if((m = malloc(sizeof(struct mio_st))) == NULL) return NULL; + if((m->fds = malloc(sizeof(struct mio_fd_st) * maxfd)) == NULL) + { + mio_debug(ZONE,"internal error creating new mio"); + free(m); + return NULL; + } + memset(m->fds, 0, sizeof(struct mio_fd_st) * maxfd); + + /* set up our internal vars */ + m->maxfd = maxfd; + m->highfd = 0; + + MIO_INIT_VARS(m); + + return m; +} + +/** adam */ +void mio_free(mio_t m) +{ + MIO_FREE_VARS(m); + + free(m->fds); + free(m); +} + +/** start processing read events */ +void mio_read(mio_t m, int fd) +{ + if(m == NULL || fd < 0) return; + + /* if connecting, do this later */ + if(FD(m,fd).type & type_CONNECT) + { + FD(m,fd).type |= type_CONNECT_READ; + return; + } + + MIO_SET_READ(m, fd); +} + +/** try writing to the socket via the app */ +void mio_write(mio_t m, int fd) +{ + if(m == NULL || fd < 0) return; + + /* if connecting, do this later */ + if(FD(m,fd).type & type_CONNECT) + { + FD(m,fd).type |= type_CONNECT_WRITE; + return; + } + + if(FD(m,fd).type != type_NORMAL) + return; + + if(ACT(m, fd, action_WRITE, NULL) == 0) return; + + /* not all written, do more l8r */ + MIO_SET_WRITE(m, fd); +} + +/** set up a listener in this mio w/ this default app/arg */ +int mio_listen(mio_t m, int port, char *sourceip, mio_handler_t app, void *arg) +{ + int fd, flag = 1; + struct sockaddr_storage sa; + + if(m == NULL) return -1; + + mio_debug(ZONE, "mio to listen on %d [%s]", port, sourceip); + + memset(&sa, 0, sizeof(sa)); + + /* if we specified an ip to bind to */ + if(sourceip != NULL && !j_inet_pton(sourceip, &sa)) + return -1; + + if(sa.ss_family == 0) + sa.ss_family = AF_INET; + + /* attempt to create a socket */ + if((fd = socket(sa.ss_family,SOCK_STREAM,0)) < 0) return -1; + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&flag, sizeof(flag)) < 0) return -1; + + /* set up and bind address info */ + j_inet_setport(&sa, port); + if(bind(fd,(struct sockaddr*)&sa,j_inet_addrlen(&sa)) < 0) + { + close(fd); + return -1; + } + + /* start listening with a max accept queue of 10 */ + if(listen(fd, 10) < 0) + { + close(fd); + return -1; + } + + /* now set us up the bomb */ + if(mio_fd(m, fd, app, arg) < 0) + { + close(fd); + return -1; + } + FD(m,fd).type = type_LISTEN; + /* by default we read for new sockets */ + mio_read(m,fd); + + return fd; +} + +/** create an fd and connect to the given ip/port */ +int mio_connect(mio_t m, int port, char *hostip, mio_handler_t app, void *arg) +{ + int fd, flag, flags; + struct sockaddr_storage sa; + + memset(&sa, 0, sizeof(sa)); + + if(m == NULL || port <= 0 || hostip == NULL) return -1; + + mio_debug(ZONE, "mio connecting to %s, port=%d",hostip,port); + + /* convert the hostip */ + if(j_inet_pton(hostip, &sa)<=0) + return -1; + + /* attempt to create a socket */ + if((fd = socket(sa.ss_family,SOCK_STREAM,0)) < 0) return -1; + + /* set the socket to non-blocking before connecting */ +#if defined(HAVE_FCNTL) + flags = fcntl(fd, F_GETFL); + flags |= O_NONBLOCK; + fcntl(fd, F_SETFL, flags); +#elif defined(HAVE_IOCTL) + flags = 1; + ioctl(fd, FIONBIO, &flags); +#endif + + /* set up address info */ + j_inet_setport(&sa, port); + + /* try to connect */ + flag = connect(fd,(struct sockaddr*)&sa,j_inet_addrlen(&sa)); + + mio_debug(ZONE, "connect returned %d and %s", flag, strerror(errno)); + + /* already connected? great! */ + if(flag == 0 && mio_fd(m,fd,app,arg) == fd) return fd; + + /* gotta wait till later */ + if(flag == -1 && errno == EINPROGRESS && mio_fd(m,fd,app,arg) == fd) + { + mio_debug(ZONE, "connect processing non-blocking mode"); + + FD(m,fd).type = type_CONNECT; + MIO_SET_WRITE(m,fd); + return fd; + } + + /* bummer dude */ + close(fd); + return -1; +} + diff --git a/mio/mio.h b/mio/mio.h new file mode 100644 index 00000000..f63d822b --- /dev/null +++ b/mio/mio.h @@ -0,0 +1,117 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifndef INCL_MIO_H +#define INCL_MIO_H + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ac-stdint.h" + +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef HAVE_SYS_SOCKET_H +# include +#endif + +#ifdef HAVE_FCNTL_H +# include +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include +#endif + +#ifdef HAVE_SYS_FILIO_H +# include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file mio/mio.h + * @brief mio - manage i/o + * + * This used to be something large and all inclusive for 1.2/1.4, + * but for 1.5 and beyond it is the most simple fd wrapper possible. + * It is also customized per-app and may be limited/extended depending on needs. + * + * Usage is pretty simple: + * - create a manager + * - add fds or tell it to listen + * - assign an action handler + * - tell mio to read or write with a fd + * - process accept, read, write, and close requests + * + * Note: normal fd's don't get events unless the app calls mio_read/write() first! + */ + +/** the master mio mama, defined internally */ +typedef struct mio_st *mio_t; + +/** these are the actions and a handler type assigned by the applicaiton using mio */ +typedef enum { action_ACCEPT, action_READ, action_WRITE, action_CLOSE } mio_action_t; +typedef int (*mio_handler_t) (mio_t m, mio_action_t a, int fd, void* data, void *arg); + +/** create/free the mio subsytem */ +mio_t mio_new(int maxfd); /* returns NULL if failed */ +void mio_free(mio_t m); + +/** for creating a new listen socket in this mio (returns new fd or <0) */ +int mio_listen(mio_t m, int port, char *sourceip, mio_handler_t app, void *arg); + +/** for creating a new socket connected to this ip:port (returns new fd or <0, use mio_read/write first) */ +int mio_connect(mio_t m, int port, char *hostip, mio_handler_t app, void *arg); + +/** tell mio to track this fd (returns new fd or <0) */ +int mio_fd(mio_t m, int fd, mio_handler_t app, void *arg); + +/** re-set the app handler */ +void mio_app(mio_t m, int fd, mio_handler_t app, void *arg); + +/** request that mio close this fd */ +void mio_close(mio_t m, int fd); + +/** mio should try the write action on this fd now */ +void mio_write(mio_t m, int fd); + +/** process read events for this fd */ +void mio_read(mio_t m, int fd); + +/** give some cpu time to mio to check it's sockets, 0 is non-blocking */ +void mio_run(mio_t m, int timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* INCL_MIO_H */ + diff --git a/mio/mio_poll.h b/mio/mio_poll.h new file mode 100644 index 00000000..51b53287 --- /dev/null +++ b/mio/mio_poll.h @@ -0,0 +1,72 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* MIO backend for poll() */ + +#ifdef HAVE_POLL_H +# include +#endif + +#define MIO_FUNCS \ + static void _mio_pfds_init(mio_t m) \ + { \ + int fd; \ + for(fd = 0; fd < m->maxfd; fd++) \ + m->pfds[fd].fd = -1; \ + } \ + \ + static int _mio_poll(mio_t m, int t) \ + { \ + return poll(m->pfds, m->highfd + 1, t*1000); \ + } + +#define MIO_VARS \ + struct pollfd *pfds; + +#define MIO_INIT_VARS(m) \ + do { \ + if((m->pfds = malloc(sizeof(struct pollfd) * maxfd)) == NULL) \ + { \ + mio_debug(ZONE, "internal error creating new mio"); \ + free(m->fds); \ + free(m); \ + return NULL; \ + } \ + memset(m->pfds, 0, sizeof(struct pollfd) * maxfd); \ + _mio_pfds_init(m); \ + } while(0) +#define MIO_FREE_VARS(m) free(m->pfds) + +#define MIO_INIT_FD(m, pfd) m->pfds[pfd].fd = pfd; m->pfds[pfd].events = 0 + +#define MIO_REMOVE_FD(m, pfd) m->pfds[pfd].fd = -1 + +#define MIO_CHECK(m, t) _mio_poll(m, t) + +#define MIO_SET_READ(m, fd) m->pfds[fd].events |= POLLIN +#define MIO_SET_WRITE(m, fd) m->pfds[fd].events |= POLLOUT + +#define MIO_UNSET_READ(m, fd) m->pfds[fd].events &= ~POLLIN +#define MIO_UNSET_WRITE(m, fd) m->pfds[fd].events &= ~POLLOUT + +#define MIO_CAN_READ(m, fd) m->pfds[fd].revents & (POLLIN|POLLERR|POLLHUP|POLLNVAL) +#define MIO_CAN_WRITE(m, fd) m->pfds[fd].revents & POLLOUT + +#define MIO_ERROR(m) errno diff --git a/mio/mio_select.h b/mio/mio_select.h new file mode 100644 index 00000000..e370de2f --- /dev/null +++ b/mio/mio_select.h @@ -0,0 +1,64 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* MIO backend for select() */ + +#ifdef HAVE_SYS_SELECT_H +# include +#endif + +#define MIO_FUNCS \ + static int _mio_select(mio_t m, int t) \ + { \ + struct timeval tv; \ + \ + m->rfds_out = m->rfds_in; \ + m->wfds_out = m->wfds_in; \ + \ + tv.tv_sec = t; \ + tv.tv_usec = 0; \ + return select(m->highfd + 1, &m->rfds_out, &m->wfds_out, NULL, &tv); \ + } + +#define MIO_VARS \ + fd_set rfds_in, wfds_in, rfds_out, wfds_out; + +#define MIO_INIT_VARS(m) \ + FD_ZERO(&m->rfds_in); \ + FD_ZERO(&m->wfds_in); + +#define MIO_FREE_VARS(m) + +#define MIO_INIT_FD(m, fd) + +#define MIO_REMOVE_FD(m, fd) do { FD_CLR(fd, &m->rfds_in); FD_CLR(fd, &m->wfds_in); } while(0) + +#define MIO_CHECK(m, t) _mio_select(m, t) + +#define MIO_SET_READ(m, fd) FD_SET(fd, &m->rfds_in) +#define MIO_SET_WRITE(m, fd) FD_SET(fd, &m->wfds_in) + +#define MIO_UNSET_READ(m, fd) FD_CLR(fd, &m->rfds_in) +#define MIO_UNSET_WRITE(m, fd) FD_CLR(fd, &m->wfds_in) + +#define MIO_CAN_READ(m, fd) FD_ISSET(fd, &m->rfds_out) +#define MIO_CAN_WRITE(m, fd) FD_ISSET(fd, &m->wfds_out) + +#define MIO_ERROR(m) errno diff --git a/resolver/.cvsignore b/resolver/.cvsignore new file mode 100644 index 00000000..40df84f9 --- /dev/null +++ b/resolver/.cvsignore @@ -0,0 +1,5 @@ +resolver +Makefile +Makefile.in +.deps +.libs diff --git a/resolver/Makefile.am b/resolver/Makefile.am new file mode 100644 index 00000000..61344fd8 --- /dev/null +++ b/resolver/Makefile.am @@ -0,0 +1,12 @@ +INCLUDES = -DCONFIG_DIR=\"$(sysconfdir)\" + +bin_PROGRAMS = resolver + +noinst_HEADERS = resolver.h dns.h +resolver_SOURCES = resolver.c dns.c + +resolver_LDADD = $(top_builddir)/sx/libsx.la \ + $(top_builddir)/mio/libmio.la \ + $(top_builddir)/util/libutil.la \ + $(top_builddir)/subst/libsubst.la \ + $(top_builddir)/expat/libexpat.la diff --git a/resolver/TODO b/resolver/TODO new file mode 100644 index 00000000..7db3ff13 --- /dev/null +++ b/resolver/TODO @@ -0,0 +1,17 @@ + - Make sure /etc/hosts gets used before we go to the network. + + It seems that under some (all?) circumstances, the res_* functions + are DNS ONLY - ie, they ignore /etc/nsswitch.conf and /etc/hosts. + I can't confirm this - documentation on the resolver is sketchy (no + surprise there). + + If res_* can't be made to use /etc/nsswitch.conf and /etc/hosts on + ALL systems, then we have two options: + + - Load /etc/nsswitch.conf and /etc/hosts ourself, and search there + before hitting the network. + + - Don't do SRV lookups at all if there's no nameserver specified in + /etc/resolv.conf, and use gethostbyname() for the A lookup. + + Both these suck. diff --git a/resolver/dns.c b/resolver/dns.c new file mode 100644 index 00000000..adafbcf9 --- /dev/null +++ b/resolver/dns.c @@ -0,0 +1,448 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "resolver.h" +#include "dns.h" + +/* Mac OS X 10.3 needs this - I don't think it will break anything else */ +#define BIND_8_COMPAT (1) + +#include +#include + +#ifdef HAVE_NETINET_IN_H +# include +#endif +#ifdef HAVE_ARPA_NAMESER_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif +#ifdef HAVE_RESOLV_H +# include +#endif +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#ifdef HAVE_WINSOCK2_H +# include +#endif +#ifdef HAVE_WINDNS_H +# include +#endif + + +/* compare two srv structures, order by priority then by randomised weight */ +static int _srv_compare(const void *a, const void *b) { + dns_host_t ah = * (dns_host_t *) a, bh = * (dns_host_t *) b; + dns_srv_t arr, brr; + + if(ah == NULL) return 1; + if(bh == NULL) return -1; + + arr = (dns_srv_t) ah->rr; + brr = (dns_srv_t) bh->rr; + + if(arr->priority > brr->priority) return 1; + if(arr->priority < brr->priority) return -1; + + if(arr->rweight > brr->rweight) return -1; + if(arr->rweight < brr->rweight) return 1; + + return 0; +} + + +/* unix implementation */ +#ifdef HAVE_RES_QUERY + +/* older systems might not have these */ +#ifndef T_SRV +# define T_SRV (33) +#endif +#ifndef T_AAAA +# define T_AAAA (28) +#endif + +/* the largest packet we'll send and receive */ +#if PACKETSZ > 1024 +# define MAX_PACKET PACKETSZ +#else +# define MAX_PACKET (1024) +#endif + +typedef union { + HEADER hdr; + unsigned char buf[MAX_PACKET]; +} dns_packet_t; + +static void *_a_rr(dns_packet_t packet, unsigned char *eom, unsigned char **scan) { + struct in_addr in; + + GETLONG(in.s_addr, *scan); + in.s_addr = ntohl(in.s_addr); + + return strdup(inet_ntoa(in)); +} + +static void *_aaaa_rr(dns_packet_t packet, unsigned char *eom, unsigned char **scan) { + char addr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 sa6; + int i; + + memset(&sa6, 0, sizeof(sa6)); + sa6.sin6_family = AF_INET6; +#ifdef SIN6_LEN + sa6.sin6_len = sizeof(sa6); +#endif + + for(i = 0; i < 16; i++) { + sa6.sin6_addr.s6_addr[i] = (*scan)[i]; + } + + j_inet_ntop((struct sockaddr_storage *)&sa6, addr, sizeof(addr)); + + return strdup(addr); +} + +static void *_srv_rr(dns_packet_t packet, unsigned char *eom, unsigned char **scan) { + unsigned int priority, weight, port; + int len; + char host[256]; + dns_srv_t srv; + + GETSHORT(priority, *scan); + GETSHORT(weight, *scan); + GETSHORT(port, *scan); + + len = dn_expand(packet.buf, eom, *scan, host, 256); + if (len < 0) + return NULL; + *scan = (unsigned char *) (*scan + len); + + srv = (dns_srv_t) malloc(sizeof(struct dns_srv_st)); + + srv->priority = priority; + srv->weight = weight; + srv->port = port; + + /* add a random factor to the weight, for load balancing and such */ + if(weight != 0) + srv->rweight = 1 + rand() % (10000 * weight); + else + srv->rweight = 0; + + strcpy(srv->name, host); + + return (void *) srv; +} + +/** the actual resolver function */ +dns_host_t dns_resolve(const char *zone, int query_type) { + char host[256]; + dns_packet_t packet; + int len, qdcount, ancount, an, n; + unsigned char *eom, *scan; + dns_host_t *reply, first; + unsigned int t_type, type, class, ttl; + + if(zone == NULL || *zone == '\0') + return NULL; + + switch(query_type) + { + case DNS_QUERY_TYPE_A: + t_type = T_A; + break; + + case DNS_QUERY_TYPE_AAAA: + t_type = T_AAAA; + break; + + case DNS_QUERY_TYPE_SRV: + t_type = T_SRV; + break; + + default: + return NULL; + } + + /* do the actual query */ + if((len = res_query(zone, C_IN, t_type, packet.buf, MAX_PACKET)) == -1 || len < sizeof(HEADER)) + return NULL; + + /* we got a valid result, containing two types of records - packet + * and answer .. we have to skip over the packet records */ + + /* no. of packets, no. of answers */ + qdcount = ntohs(packet.hdr.qdcount); + ancount = ntohs(packet.hdr.ancount); + + /* end of the returned message */ + eom = (unsigned char *) (packet.buf + len); + + /* our current location */ + scan = (unsigned char *) (packet.buf + sizeof(HEADER)); + + /* skip over the packet records */ + while(qdcount > 0 && scan < eom) { + qdcount--; + if((len = dn_expand(packet.buf, eom, scan, host, 256)) < 0) + return NULL; + scan = (unsigned char *) (scan + len + QFIXEDSZ); + } + + /* create an array to store the replies in */ + reply = (dns_host_t *) malloc(sizeof(dns_host_t) * ancount); + memset(reply, 0, sizeof(dns_host_t) * ancount); + + an = 0; + /* loop through the answer buffer and extract SRV records */ + while(ancount > 0 && scan < eom ) { + ancount--; + len = dn_expand(packet.buf, eom, scan, host, 256); + if(len < 0) { + for(n = 0; n < an; n++) + free(reply[n]); + free(reply); + return NULL; + } + + scan += len; + + /* extract the various parts of the record */ + GETSHORT(type, scan); + GETSHORT(class, scan); + GETLONG(ttl, scan); + GETSHORT(len, scan); + + /* skip records we're not interested in */ + if(type != t_type) { + scan = (unsigned char *) (scan + len); + continue; + } + + /* create a new reply structure to save it in */ + reply[an] = (dns_host_t) malloc(sizeof(struct dns_host_st)); + + reply[an]->type = type; + reply[an]->class = class; + reply[an]->ttl = ttl; + + reply[an]->next = NULL; + + /* type-specific processing */ + switch(type) + { + case T_A: + reply[an]->rr = _a_rr(packet, eom, &scan); + break; + + case T_AAAA: + reply[an]->rr = _aaaa_rr(packet, eom, &scan); + break; + + case T_SRV: + reply[an]->rr = _srv_rr(packet, eom, &scan); + break; + + default: + scan = (unsigned char *) (scan + len); + continue; + } + + /* fell short, we're done */ + if(reply[an]->rr == NULL) + { + free(reply[an]); + reply[an] = NULL; + break; + } + + /* on to the next one */ + an++; + } + + /* sort srv records them */ + if(t_type == T_SRV) + qsort(reply, an, sizeof(dns_host_t), _srv_compare); + + /* build a linked list out of the array elements */ + for(n = 0; n < an - 1; n++) + reply[n]->next = reply[n + 1]; + + first = reply[0]; + + free(reply); + + return first; +} + +#endif /* HAVE_RES_QUERY */ + +/* windows implementation */ +#ifdef HAVE_DNSQUERY + +/* mingw doesn't have these, and msdn doesn't document them. hmph. */ +#ifndef DNS_TYPE_SRV +# define DNS_TYPE_SRV (33) +#endif +#ifndef DNS_TYPE_AAAA +# define DNS_TYPE_AAAA (28) +#endif + +static void *_a_rr(DNS_A_DATA *data) { + struct in_addr in; + + in.s_addr = data->IpAddress; + + return strdup(inet_ntoa(in)); +} + +static void *_aaaa_rr(DNS_AAAA_DATA *data) { + char addr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 sa6; + int i; + + memset(&sa6, 0, sizeof(sa6)); + sa6.sin6_family = AF_INET6; +#ifdef SIN6_LEN + sa6.sin6_len = sizeof(sa6); +#endif + + for(i = 0; i < 4; i++) + sa6.sin6_addr.s6_addr32[i] = data->Ip6Address.IP6Dword[i]; + + j_inet_ntop((struct sockaddr_storage *) &sa6, addr, sizeof(addr)); + + return strdup(addr); +} + +static void *_srv_rr(DNS_SRV_DATA *data) { + dns_srv_t srv; + + srv = (dns_srv_t) malloc(sizeof(struct dns_srv_st)); + + srv->priority = data->wPriority; + srv->weight = data->wWeight; + srv->port = data->wPort; + + if(srv->weight != 0) + srv->rweight = 1 + rand() % (10000 * srv->weight); + else + srv->rweight = 0; + + strncpy(srv->name, data->pNameTarget, 255); + srv->name[255] = 0; + + return (void *) srv; +} + +dns_host_t dns_resolve(const char *zone, int query_type) { + int type, num, i; + PDNS_RECORD rr, scan; + dns_host_t *reply, first; + + if(zone == NULL || *zone == '\0') + return NULL; + + switch(query_type) { + case DNS_QUERY_TYPE_A: + type = DNS_TYPE_A; + break; + + case DNS_QUERY_TYPE_AAAA: + type = DNS_TYPE_AAAA; + break; + + case DNS_QUERY_TYPE_SRV: + type = DNS_TYPE_SRV; + break; + + default: + return NULL; + } + + if(DnsQuery(zone, type, DNS_QUERY_STANDARD, NULL, &rr, NULL) != 0) + return NULL; + + num = 0; + for(scan = rr; scan != NULL; scan = scan->pNext) + num++; + + reply = (dns_host_t *) malloc(sizeof(dns_host_t) * num); + memset(reply, 0, sizeof(dns_host_t) * num); + + num = 0; + for(scan = rr; scan != NULL; scan = scan->pNext) { + if(scan->wType != type || stricmp(scan->pName, zone) != 0) + continue; + + reply[num] = (dns_host_t) malloc(sizeof(struct dns_host_st)); + + reply[num]->type = scan->wType; + reply[num]->class = 0; + reply[num]->ttl = scan->dwTtl; + + reply[num]->next = NULL; + + switch(type) { + case DNS_TYPE_A: + reply[num]->rr = _a_rr(&scan->Data.A); + break; + + case DNS_TYPE_AAAA: + reply[num]->rr = _aaaa_rr(&scan->Data.AAAA); + break; + + case DNS_TYPE_SRV: + reply[num]->rr = _srv_rr(&scan->Data.SRV); + break; + } + + num++; + } + + if(type == DNS_TYPE_SRV) + qsort(reply, num, sizeof(dns_host_t), _srv_compare); + + for(i = 0; i < num - 1; i++) + reply[i]->next = reply[i + 1]; + + first = reply[0]; + + free(reply); + + return first; +} +#endif /* HAVE_DNSQUERY */ + +/** free an srv structure */ +void dns_free(dns_host_t dns) { + dns_host_t next; + + while(dns != NULL) { + next = dns->next; + free(dns->rr); + free(dns); + dns = next; + } +} diff --git a/resolver/dns.h b/resolver/dns.h new file mode 100644 index 00000000..e077b108 --- /dev/null +++ b/resolver/dns.h @@ -0,0 +1,55 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifndef DNS_H +#define DNS_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define DNS_QUERY_TYPE_A (1) +#define DNS_QUERY_TYPE_AAAA (2) +#define DNS_QUERY_TYPE_SRV (3) + +typedef struct dns_host_st { + struct dns_host_st *next; + + unsigned int type; + unsigned int class; + unsigned int ttl; + + void *rr; +} *dns_host_t; + +typedef struct dns_srv_st { + unsigned int priority; + unsigned int weight; + unsigned int port; + unsigned int rweight; + + char name[256]; +} *dns_srv_t; + +extern dns_host_t dns_resolve(const char *zone, int type); +extern void dns_free(dns_host_t dns); + +#endif + diff --git a/resolver/resolver.c b/resolver/resolver.c new file mode 100644 index 00000000..f0447eb6 --- /dev/null +++ b/resolver/resolver.c @@ -0,0 +1,685 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "resolver.h" + +static sig_atomic_t resolver_shutdown = 0; +static sig_atomic_t resolver_lost_router = 0; +static sig_atomic_t resolver_logrotate = 0; + +static void _resolver_signal(int signum) +{ + resolver_shutdown = 1; + resolver_lost_router = 0; +} + +static void _resolver_signal_hup(int signum) +{ + resolver_logrotate = 1; +} + +/** store the process id */ +static void _resolver_pidfile(resolver_t r) { + char *pidfile; + FILE *f; + pid_t pid; + + pidfile = config_get_one(r->config, "pidfile", 0); + if(pidfile == NULL) + return; + + pid = getpid(); + + if((f = fopen(pidfile, "w+")) == NULL) { + log_write(r->log, LOG_ERR, "couldn't open %s for writing: %s", pidfile, strerror(errno)); + return; + } + + if(fprintf(f, "%d", pid) < 0) { + log_write(r->log, LOG_ERR, "couldn't write to %s: %s", pidfile, strerror(errno)); + return; + } + + fclose(f); + + log_write(r->log, LOG_INFO, "process id is %d, written to %s", pid, pidfile); +} + +/** pull values out of the config file */ +static void _resolver_config_expand(resolver_t r) +{ + char *str; + config_elem_t elem; + + r->id = config_get_one(r->config, "id", 0); + if(r->id == NULL) + r->id = "resolver"; + + r->router_ip = config_get_one(r->config, "router.ip", 0); + if(r->router_ip == NULL) + r->router_ip = "127.0.0.1"; + + r->router_port = j_atoi(config_get_one(r->config, "router.port", 0), 5347); + + r->router_user = config_get_one(r->config, "router.user", 0); + if(r->router_user == NULL) + r->router_user = "jabberd"; + r->router_pass = config_get_one(r->config, "router.pass", 0); + if(r->router_pass == NULL) + r->router_pass = "secret"; + + r->router_pemfile = config_get_one(r->config, "router.pemfile", 0); + + r->retry_init = j_atoi(config_get_one(r->config, "router.retry.init", 0), 3); + r->retry_lost = j_atoi(config_get_one(r->config, "router.retry.lost", 0), 3); + if((r->retry_sleep = j_atoi(config_get_one(r->config, "router.retry.sleep", 0), 2)) < 1) + r->retry_sleep = 1; + + r->log_type = log_STDOUT; + if(config_get(r->config, "log") != NULL) { + if((str = config_get_attr(r->config, "log", 0, "type")) != NULL) { + if(strcmp(str, "file") == 0) + r->log_type = log_FILE; + else if(strcmp(str, "syslog") == 0) + r->log_type = log_SYSLOG; + } + } + + if(r->log_type == log_SYSLOG) { + r->log_facility = config_get_one(r->config, "log.facility", 0); + r->log_ident = config_get_one(r->config, "log.ident", 0); + if(r->log_ident == NULL) + r->log_ident = "jabberd/resolver"; + } else if(r->log_type == log_FILE) + r->log_ident = config_get_one(r->config, "log.file", 0); + + if((elem = config_get(r->config, "lookup.srv")) != NULL) { + r->lookup_srv = elem->values; + r->lookup_nsrv = elem->nvalues; + } + + r->resolve_aaaa = config_count(r->config, "ipv6") ? 1 : 0; +} + +static int _resolver_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + resolver_t r = (resolver_t) arg; + sx_buf_t buf = (sx_buf_t) data; + sx_error_t *sxe; + int elem, len, attr, ns, aname, eip, srv, nres; + nad_t nad; + char zone[256], num[10]; + dns_host_t srvs, srvscan, as, ascan; + + switch(e) { + case event_WANT_READ: + log_debug(ZONE, "want read"); + mio_read(r->mio, r->fd); + break; + + case event_WANT_WRITE: + log_debug(ZONE, "want write"); + mio_write(r->mio, r->fd); + break; + + case event_READ: + log_debug(ZONE, "reading from %d", r->fd); + + /* do the read */ + len = recv(r->fd, buf->data, buf->len, 0); + + if(len < 0) { + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) { + buf->len = 0; + return 0; + } + + log_write(r->log, LOG_NOTICE, "[%d] [router] read error: %s (%d)", r->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + } + + else if(len == 0) { + /* they went away */ + sx_kill(s); + + return -1; + } + + log_debug(ZONE, "read %d bytes", len); + + buf->len = len; + + return len; + + case event_WRITE: + log_debug(ZONE, "writing to %d", r->fd); + + len = send(r->fd, buf->data, buf->len, 0); + if(len >= 0) { + log_debug(ZONE, "%d bytes written", len); + return len; + } + + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) + return 0; + + log_write(r->log, LOG_NOTICE, "[%d] [router] write error: %s (%d)", r->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + + case event_ERROR: + sxe = (sx_error_t *) data; + log_write(r->log, LOG_NOTICE, "error from router: %s (%s)", sxe->generic, sxe->specific); + + if(sxe->code == SX_ERR_AUTH) + sx_close(s); + + break; + + case event_STREAM: + break; + + case event_OPEN: + log_write(r->log, LOG_NOTICE, "connection to router established"); + + /* reset connection attempts counter */ + r->retry_left = r->retry_init; + + nad = nad_new(r->router->nad_cache); + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, ns, "bind", 0); + nad_append_attr(nad, -1, "name", r->id); + + log_debug(ZONE, "requesting component bind for '%s'", r->id); + + sx_nad_write(r->router, nad); + + break; + + case event_PACKET: + nad = (nad_t) data; + + /* drop unqualified packets */ + if(NAD_ENS(nad, 0) < 0) { + nad_free(nad); + return 0; + } + + /* watch for the features packet */ + if(s->state == state_STREAM) { + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_STREAMS) || strncmp(uri_STREAMS, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_STREAMS)) != 0 || NAD_ENAME_L(nad, 0) != 8 || strncmp("features", NAD_ENAME(nad, 0), 8)) { + log_debug(ZONE, "got a non-features packet on an unauth'd stream, dropping"); + nad_free(nad); + return 0; + } + +#ifdef HAVE_SSL + /* starttls if we can */ + if(r->sx_ssl != NULL && s->ssf == 0) { + ns = nad_find_scoped_namespace(nad, uri_TLS, NULL); + if(ns >= 0) { + elem = nad_find_elem(nad, 0, ns, "starttls", 1); + if(elem >= 0) { + if(sx_ssl_client_starttls(r->sx_ssl, s, NULL) == 0) { + nad_free(nad); + return 0; + } + log_write(r->log, LOG_NOTICE, "unable to establish encrypted session with router"); + } + } + } +#endif + + /* !!! pull the list of mechanirs, and choose the best one. + * if there isn't an appropriate one, error and bail */ + + /* authenticate */ + sx_sasl_auth(r->sx_sasl, s, "jabberd-router", "DIGEST-MD5", r->router_user, r->router_pass); + + nad_free(nad); + return 0; + } + + /* watch for the bind response */ + if(s->state == state_OPEN && !r->online) { + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_COMPONENT) || strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) != 0 || NAD_ENAME_L(nad, 0) != 4 || strncmp("bind", NAD_ENAME(nad, 0), 4)) { + log_debug(ZONE, "got a packet from router, but we're not online, dropping"); + nad_free(nad); + return 0; + } + + /* catch errors */ + attr = nad_find_attr(nad, 0, -1, "error", NULL); + if(attr >= 0) { + log_write(r->log, LOG_NOTICE, "router refused bind request (%.*s)", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + exit(1); + } + + log_debug(ZONE, "coming online"); + + /* we're online */ + r->online = r->started = 1; + r->retry_left = r->retry_lost; + + log_write(r->log, LOG_NOTICE, "ready to resolve", r->id); + + nad_free(nad); + return 0; + } + + /* drop errors */ + if(nad_find_attr(nad, 1, -1, "type", "error") >= 0) { + nad_free(nad); + return 0; + } + + /* check packet, extract needed info */ + if(!( + /* subpacket exists */ + nad->ecur > 1 && + /* packet is in the component namespace */ + (NAD_NURI_L(nad, NAD_ENS(nad, 0)) == strlen(uri_COMPONENT) && strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) == 0) && + /* packet is a route */ + (NAD_ENAME_L(nad, 0) == 5 && strncmp("route", NAD_ENAME(nad, 0), 5) == 0) && + /* subpacket is in the resolver namespace */ + (NAD_NURI_L(nad, NAD_ENS(nad, 1)) == strlen(uri_RESOLVER) && strncmp(uri_RESOLVER, NAD_NURI(nad, NAD_ENS(nad, 1)), strlen(uri_RESOLVER)) == 0) && + /* packet has a subelement "resolve" in that namespace */ + (NAD_ENAME_L(nad, 1) == 7 && strncmp("resolve", NAD_ENAME(nad, 1), 7) == 0) && + /* resolve has a "type" of "query" */ + nad_find_attr(nad, 1, -1, "type", "query") >= 0 && + /* resolve has a "name" attribute */ + (aname = nad_find_attr(nad, 1, -1, "name", NULL)) >= 0)) + { + /* yes, we're very intolerent of people who can't speak to us properly .. we are internal, after all */ + nad_free(nad); + return 0; + } + + srv = 0; nres = 0; + while(srv < r->lookup_nsrv && nres == 0) { + /* do the lookup */ + snprintf(zone, 256, "%s.%.*s", r->lookup_srv[srv], NAD_AVAL_L(nad, aname), NAD_AVAL(nad, aname)); + + log_debug(ZONE, "trying srv lookup for %s", zone); + + srvs = dns_resolve(zone, DNS_QUERY_TYPE_SRV); + + if(srvs != NULL) { + /* resolve to A records */ + for(srvscan = srvs; srvscan != NULL; srvscan = srvscan->next) { + log_debug(ZONE, "%s has srv %s, doing A lookup", zone, ((dns_srv_t) srvscan->rr)->name); + + as = dns_resolve(((dns_srv_t) srvscan->rr)->name, DNS_QUERY_TYPE_A); + + for(ascan = as; ascan != NULL; ascan = ascan->next) { + log_write(r->log, LOG_NOTICE, "[%s] resolved to %s:%d (%d seconds to live)", zone, (char *) ascan->rr, ((dns_srv_t) srvscan->rr)->port, ascan->ttl); + + eip = nad_insert_elem(nad, 1, NAD_ENS(nad, 1), "ip", (char *) ascan->rr); + + snprintf(num, 10, "%d", ((dns_srv_t) srvscan->rr)->port); + nad_set_attr(nad, eip, -1, "port", num, 0); + + snprintf(num, 10, "%d", ascan->ttl); + nad_set_attr(nad, eip, -1, "ttl", num, 0); + + nres++; + } + + dns_free(as); + } + + /* resolve to AAAA records */ + if(r->resolve_aaaa) { + for(srvscan = srvs; srvscan != NULL; srvscan = srvscan->next) { + log_debug(ZONE, "%s has srv %s, doing AAAA lookup", zone, ((dns_srv_t) srvscan->rr)->name); + + as = dns_resolve(((dns_srv_t) srvscan->rr)->name, DNS_QUERY_TYPE_AAAA); + + for(ascan = as; ascan != NULL; ascan = ascan->next) { + log_write(r->log, LOG_NOTICE, "[%s] resolved to [%s]:%d (%d seconds to live)", zone, (char *)ascan->rr, ((dns_srv_t) srvscan->rr)->port, ascan->ttl); + + eip = nad_insert_elem(nad, 1, NAD_ENS(nad, 1), "ip", (char *)ascan->rr); + + snprintf(num, 10, "%d", ((dns_srv_t) srvscan->rr)->port); + nad_set_attr(nad, eip, -1, "port", num, 0); + + snprintf(num, 10, "%d", ascan->ttl); + nad_set_attr(nad, eip, -1, "ttl", num, 0); + + nres++; + } + + dns_free(as); + } + } + + dns_free(srvs); + } + + srv++; + } + + /* AAAA/A fallback */ + if(nres == 0) { + snprintf(zone, 256, "%.*s", NAD_AVAL_L(nad, aname), NAD_AVAL(nad, aname)); + + /* A lookup */ + log_debug(ZONE, "doing A lookup for %s", zone); + + as = dns_resolve(zone, DNS_QUERY_TYPE_A); + for(ascan = as; ascan != NULL; ascan = ascan->next) { + log_write(r->log, LOG_NOTICE, "[%s] resolved to [%s:5269] (%d seconds to live)", zone, (char *) ascan->rr, ascan->ttl); + + eip = nad_insert_elem(nad, 1, NAD_ENS(nad, 1), "ip", (char *) ascan->rr); + + nad_set_attr(nad, eip, -1, "port", "5269", 4); + + snprintf(num, 10, "%d", ascan->ttl); + nad_set_attr(nad, eip, -1, "ttl", num, 0); + + nres++; + } + + dns_free(as); + + /* AAAA lookup */ + if(r->resolve_aaaa) { + log_debug(ZONE, "doing AAAA lookup for %s", zone); + + as = dns_resolve(zone, DNS_QUERY_TYPE_AAAA); + for(ascan = as; ascan != NULL; ascan = ascan->next) + { + log_write(r->log, LOG_NOTICE, "[%s] resolved to [%s]:5269 (%d seconds to live)", zone, (char *)ascan->rr, ascan->ttl); + + eip = nad_insert_elem(nad, 1, NAD_ENS(nad, 1), "ip", (char *)ascan->rr); + + nad_set_attr(nad, eip, -1, "port", "5269", 4); + + snprintf(num, 10, "%d", ascan->ttl); + nad_set_attr(nad, eip, -1, "ttl", num, 0); + + nres++; + } + + dns_free(as); + } + } + + nad_set_attr(nad, 1, -1, "type", "result", 6); + sx_nad_write(r->router, stanza_tofrom(nad, 0)); + + if (nres == 0) { + log_write(r->log, LOG_NOTICE, "[%s] could not be resolved", zone); + } + + break; + + case event_CLOSED: + mio_close(r->mio, r->fd); + break; + } + + return 0; +} + +static int _resolver_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) { + resolver_t r = (resolver_t) arg; + int nbytes; + + switch(a) { + case action_READ: + + ioctl(fd, FIONREAD, &nbytes); + if(nbytes == 0) { + sx_kill(r->router); + return 0; + } + + log_debug(ZONE, "read action on fd %d", fd); + return sx_can_read(r->router); + + case action_WRITE: + log_debug(ZONE, "write action on fd %d", fd); + return sx_can_write(r->router); + + case action_CLOSE: + log_debug(ZONE, "close action on fd %d", fd); + log_write(r->log, LOG_NOTICE, "connection to router closed"); + + resolver_lost_router = 1; + + /* we're offline */ + r->online = 0; + + break; + + case action_ACCEPT: + break; + } + + return 0; +} + +static int _resolver_router_connect(resolver_t r) { + log_write(r->log, LOG_NOTICE, "attempting connection to router at %s, port=%d", r->router_ip, r->router_port); + + r->fd = mio_connect(r->mio, r->router_port, r->router_ip, _resolver_mio_callback, (void *) r); + if(r->fd < 0) { + if(errno == ECONNREFUSED) + resolver_lost_router = 1; + log_write(r->log, LOG_NOTICE, "connection attempt to router failed: %s (%d)", strerror(errno), errno); + return 1; + } + + r->router = sx_new(r->sx_env, r->fd, _resolver_sx_callback, (void *) r); + sx_client_init(r->router, 0, NULL, NULL, NULL, "1.0"); + + return 0; +} + +int main(int argc, char **argv) +{ + resolver_t r; + char *config_file; + int optchar; +#ifdef POOL_DEBUG + time_t pool_time = 0; +#endif + +#ifdef HAVE_UMASK + umask((mode_t) 0027); +#endif + + srand(time(NULL)); + +#ifdef HAVE_WINSOCK2_H +/* get winsock running */ + { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + /* !!! tell user that we couldn't find a usable winsock dll */ + return 0; + } + } +#endif + + jabber_signal(SIGINT, _resolver_signal); + jabber_signal(SIGTERM, _resolver_signal); +#ifdef SIGHUP + jabber_signal(SIGHUP, _resolver_signal_hup); +#endif +#ifdef SIGPIPE + jabber_signal(SIGPIPE, SIG_IGN); +#endif + + r = (resolver_t) malloc(sizeof(struct resolver_st)); + memset(r, 0, sizeof(struct resolver_st)); + + /* load our config */ + r->config = config_new(); + + config_file = CONFIG_DIR "/resolver.xml"; + + /* cmdline parsing */ + while((optchar = getopt(argc, argv, "Dc:h?")) >= 0) + { + switch(optchar) + { + case 'c': + config_file = optarg; + break; + case 'D': +#ifdef DEBUG + set_debug_flag(1); +#else + printf("WARN: Debugging not enabled. Ignoring -D.\n"); +#endif + break; + case 'h': case '?': default: + fputs( + "resolver - jabberd asynchronous dns resolver (" VERSION ")\n" + "Usage: resolver \n" + "Options are:\n" + " -c config file to use [default: " CONFIG_DIR "/resolver.xml]\n" +#ifdef DEBUG + " -D Show debug output\n" +#endif + , + stdout); + config_free(r->config); + free(r); + return 1; + } + } + + if(config_load(r->config, config_file) != 0) + { + fputs("resolver: couldn't load config, aborting\n", stderr); + config_free(r->config); + free(r); + return 2; + } + + _resolver_config_expand(r); + + r->log = log_new(r->log_type, r->log_ident, r->log_facility); + log_write(r->log, LOG_NOTICE, "starting up"); + + _resolver_pidfile(r); + + r->sx_env = sx_env_new(); + +#ifdef HAVE_SSL + if(r->router_pemfile != NULL) { + r->sx_ssl = sx_env_plugin(r->sx_env, sx_ssl_init, r->router_pemfile, NULL); + if(r->sx_ssl == NULL) { + log_write(r->log, LOG_ERR, "failed to load SSL pemfile, SSL disabled"); + r->router_pemfile = NULL; + } + } +#endif + + /* get sasl online */ + r->sx_sasl = sx_env_plugin(r->sx_env, sx_sasl_init, "jabberd-router", NULL, NULL, 0); + if(r->sx_sasl == NULL) { + log_write(r->log, LOG_ERR, "failed to initialise SASL context, aborting"); + exit(1); + } + + r->mio = mio_new(1023); + + r->retry_left = r->retry_init; + _resolver_router_connect(r); + + while(!resolver_shutdown) { + mio_run(r->mio, 5); + + if(resolver_logrotate) { + log_write(r->log, LOG_NOTICE, "reopening log ..."); + log_free(r->log); + r->log = log_new(r->log_type, r->log_ident, r->log_facility); + log_write(r->log, LOG_NOTICE, "log started"); + + resolver_logrotate = 0; + } + + if(resolver_lost_router) { + if(r->retry_left < 0) { + log_write(r->log, LOG_NOTICE, "attempting reconnect"); + sleep(r->retry_sleep); + resolver_lost_router = 0; + _resolver_router_connect(r); + } + + else if(r->retry_left == 0) { + resolver_shutdown = 1; + } + + else { + log_write(r->log, LOG_NOTICE, "attempting reconnect (%d left)", r->retry_left); + r->retry_left--; + sleep(r->retry_sleep); + resolver_lost_router = 0; + _resolver_router_connect(r); + } + } + +#ifdef POOL_DEBUG + if(time(NULL) > pool_time + 60) { + pool_stat(1); + pool_time = time(NULL); + } +#endif + } + + log_write(r->log, LOG_NOTICE, "shutting down"); + + sx_free(r->router); + + sx_env_free(r->sx_env); + + mio_free(r->mio); + + log_free(r->log); + + config_free(r->config); + + free(r); + +#ifdef POOL_DEBUG + pool_stat(1); +#endif + +#ifdef HAVE_WINSOCK2_H + WSACleanup(); +#endif + + return 0; +} diff --git a/resolver/resolver.h b/resolver/resolver.h new file mode 100644 index 00000000..6b039082 --- /dev/null +++ b/resolver/resolver.h @@ -0,0 +1,92 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "mio/mio.h" +#include "sx/sx.h" +#include "sx/ssl.h" +#include "sx/sasl.h" +#include "util/util.h" +#include "dns.h" + +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif + +typedef struct resolver_st { + /** our id (hostname) with the router */ + char *id; + + /** how to connect to the router */ + char *router_ip; + int router_port; + char *router_user; + char *router_pass; + char *router_pemfile; + + /** mio context */ + mio_t mio; + + /** sx environment */ + sx_env_t sx_env; + sx_plugin_t sx_ssl; + sx_plugin_t sx_sasl; + + /** router's conn */ + sx_t router; + int fd; + + /** config */ + config_t config; + + /** logging */ + log_t log; + + /** log data */ + log_type_t log_type; + char *log_facility; + char *log_ident; + + /** connect retry */ + int retry_init; + int retry_lost; + int retry_sleep; + int retry_left; + + /** srvs to lookup */ + char **lookup_srv; + int lookup_nsrv; + + /** if we resolve AAAA records */ + int resolve_aaaa; + + /** this is true if we've connected to the router at least once */ + int started; + + /** true if we're currently bound in the router */ + int online; +} *resolver_t; + diff --git a/router/.cvsignore b/router/.cvsignore new file mode 100644 index 00000000..c6202d13 --- /dev/null +++ b/router/.cvsignore @@ -0,0 +1,5 @@ +router +Makefile +Makefile.in +.deps +.libs diff --git a/router/Makefile.am b/router/Makefile.am new file mode 100644 index 00000000..fbab0d9e --- /dev/null +++ b/router/Makefile.am @@ -0,0 +1,12 @@ +INCLUDES = -DCONFIG_DIR=\"$(sysconfdir)\" + +bin_PROGRAMS = router + +noinst_HEADERS = router.h +router_SOURCES = aci.c main.c router.c user.c + +router_LDADD = $(top_builddir)/sx/libsx.la \ + $(top_builddir)/mio/libmio.la \ + $(top_builddir)/util/libutil.la \ + $(top_builddir)/subst/libsubst.la \ + $(top_builddir)/expat/libexpat.la diff --git a/router/aci.c b/router/aci.c new file mode 100644 index 00000000..811f3131 --- /dev/null +++ b/router/aci.c @@ -0,0 +1,133 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "router.h" + +/** aci manager */ + +typedef struct aci_user_st *aci_user_t; +struct aci_user_st { + char *name; + aci_user_t next; +}; + +xht aci_load(router_t r) { + xht aci; + int aelem, uelem, attr; + char type[33]; + aci_user_t list_head, list_tail, user; + + log_debug(ZONE, "loading aci"); + + aci = xhash_new(51); + + if((aelem = nad_find_elem(r->config->nad, 0, -1, "aci", 1)) < 0) + return aci; + + aelem = nad_find_elem(r->config->nad, aelem, -1, "acl", 1); + while(aelem >= 0) { + if((attr = nad_find_attr(r->config->nad, aelem, -1, "type", NULL)) < 0) { + aelem = nad_find_elem(r->config->nad, aelem, -1, "acl", 0); + continue; + } + + list_head = NULL; + list_tail = NULL; + + snprintf(type, 33, "%.*s", NAD_AVAL_L(r->config->nad, attr), NAD_AVAL(r->config->nad, attr)); + + log_debug(ZONE, "building list for '%s'", type); + + uelem = nad_find_elem(r->config->nad, aelem, -1, "user", 1); + while(uelem >= 0) { + if(NAD_CDATA_L(r->config->nad, uelem) > 0) { + user = (aci_user_t) malloc(sizeof(struct aci_user_st)); + memset(user, 0, sizeof(struct aci_user_st)); + + user->name = (char *) malloc(sizeof(char) * (NAD_CDATA_L(r->config->nad, uelem) + 1)); + sprintf(user->name, "%.*s", NAD_CDATA_L(r->config->nad, uelem), NAD_CDATA(r->config->nad, uelem)); + + if(list_tail != NULL) { + list_tail->next = user; + list_tail = user; + } + + /* record the head of the list */ + if(list_head == NULL) { + list_head = user; + list_tail = user; + } + + log_debug(ZONE, "added '%s'", user->name); + } + + uelem = nad_find_elem(r->config->nad, uelem, -1, "user", 0); + } + + if(list_head != NULL) + xhash_put(aci, pstrdup(xhash_pool(aci), type), (void *) list_head); + + aelem = nad_find_elem(r->config->nad, aelem, -1, "acl", 0); + } + + return aci; +} + +/** see if a username is in an acl */ +int aci_check(xht aci, char *type, char *name) { + aci_user_t list, scan; + + log_debug(ZONE, "checking for '%s' in acl 'all'", name); + list = (aci_user_t) xhash_get(aci, "all"); + for(scan = list; scan != NULL; scan = scan->next) + if(strcmp(scan->name, name) == 0) + return 1; + + if(type != NULL) { + log_debug(ZONE, "checking for '%s' in acl '%s'", name, type); + list = (aci_user_t) xhash_get(aci, type); + for(scan = list; scan != NULL; scan = scan->next) + if(strcmp(scan->name, name) == 0) + return 1; + } + + return 0; +} + +/** unload aci table */ +void aci_unload(xht aci) { + aci_user_t list, user; + char *aclname; + + /* free list of users for each acl*/ + if(xhash_iter_first(aci)) + do { + xhash_iter_get(aci, (const char **) &aclname, (void *) &list); + while (list != NULL) { + user = list; + list = list->next; + free(user->name); + free(user); + } + } while(xhash_iter_next(aci)); + + xhash_free(aci); + return; +} diff --git a/router/main.c b/router/main.c new file mode 100644 index 00000000..8a18a46b --- /dev/null +++ b/router/main.c @@ -0,0 +1,521 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "router.h" + +static sig_atomic_t router_shutdown = 0; +static sig_atomic_t router_logrotate = 0; + +void router_signal(int signum) +{ + router_shutdown = 1; +} + +void router_signal_hup(int signum) +{ + router_logrotate = 1; +} + +/** store the process id */ +static void _router_pidfile(router_t r) { + char *pidfile; + FILE *f; + pid_t pid; + + pidfile = config_get_one(r->config, "pidfile", 0); + if(pidfile == NULL) + return; + + pid = getpid(); + + if((f = fopen(pidfile, "w+")) == NULL) { + log_write(r->log, LOG_ERR, "couldn't open %s for writing: %s", pidfile, strerror(errno)); + return; + } + + if(fprintf(f, "%d", pid) < 0) { + log_write(r->log, LOG_ERR, "couldn't write to %s: %s", pidfile, strerror(errno)); + return; + } + + fclose(f); + + log_write(r->log, LOG_INFO, "process id is %d, written to %s", pid, pidfile); +} + +/** pull values out of the config file */ +static void _router_config_expand(router_t r) +{ + char *str, *ip, *mask, *name, *target; + config_elem_t elem; + int i; + alias_t alias; + + r->id = config_get_one(r->config, "id", 0); + if(r->id == NULL) + r->id = "router"; + + r->log_type = log_STDOUT; + if(config_get(r->config, "log") != NULL) { + if((str = config_get_attr(r->config, "log", 0, "type")) != NULL) { + if(strcmp(str, "file") == 0) + r->log_type = log_FILE; + else if(strcmp(str, "syslog") == 0) + r->log_type = log_SYSLOG; + } + } + + if(r->log_type == log_SYSLOG) { + r->log_facility = config_get_one(r->config, "log.facility", 0); + r->log_ident = config_get_one(r->config, "log.ident", 0); + if(r->log_ident == NULL) + r->log_ident = "jabberd/router"; + } else if(r->log_type == log_FILE) + r->log_ident = config_get_one(r->config, "log.file", 0); + + r->local_ip = config_get_one(r->config, "local.ip", 0); + if(r->local_ip == NULL) + r->local_ip = "0.0.0.0"; + + r->local_port = j_atoi(config_get_one(r->config, "local.port", 0), 5347); + + r->local_secret = config_get_one(r->config, "local.secret", 0); + + r->local_pemfile = config_get_one(r->config, "local.pemfile", 0); + + r->io_max_fds = j_atoi(config_get_one(r->config, "io.max_fds", 0), 1024); + + elem = config_get(r->config, "io.limits.bytes"); + if(elem != NULL) + { + r->byte_rate_total = j_atoi(elem->values[0], 0); + if(r->byte_rate_total != 0) + { + r->byte_rate_seconds = j_atoi(j_attr((const char **) elem->attrs[0], "seconds"), 5); + r->byte_rate_wait = j_atoi(j_attr((const char **) elem->attrs[0], "throttle"), 5); + } + } + + elem = config_get(r->config, "io.limits.connects"); + if(elem != NULL) + { + r->conn_rate_total = j_atoi(elem->values[0], 0); + if(r->conn_rate_total != 0) + { + r->conn_rate_seconds = j_atoi(j_attr((const char **) elem->attrs[0], "seconds"), 5); + r->conn_rate_wait = j_atoi(j_attr((const char **) elem->attrs[0], "throttle"), 5); + } + } + + str = config_get_one(r->config, "io.access.order", 0); + if(str == NULL || strcmp(str, "deny,allow") != 0) + r->access = access_new(0); + else + r->access = access_new(1); + + elem = config_get(r->config, "io.access.allow"); + if(elem != NULL) + { + for(i = 0; i < elem->nvalues; i++) + { + ip = j_attr((const char **) elem->attrs[i], "ip"); + mask = j_attr((const char **) elem->attrs[i], "mask"); + + if(ip == NULL) + continue; + + if(mask == NULL) + mask = "255.255.255.255"; + + access_allow(r->access, ip, mask); + } + } + + elem = config_get(r->config, "io.access.deny"); + if(elem != NULL) + { + for(i = 0; i < elem->nvalues; i++) + { + ip = j_attr((const char **) elem->attrs[i], "ip"); + mask = j_attr((const char **) elem->attrs[i], "mask"); + + if(ip == NULL) + continue; + + if(mask == NULL) + mask = "255.255.255.255"; + + access_deny(r->access, ip, mask); + } + } + + /* aliases */ + elem = config_get(r->config, "aliases.alias"); + if(elem != NULL) + for(i = 0; i < elem->nvalues; i++) { + name = j_attr((const char **) elem->attrs[i], "name"); + target = j_attr((const char **) elem->attrs[i], "target"); + + if(name == NULL || target == NULL) + continue; + + alias = (alias_t) malloc(sizeof(struct alias_st)); + memset(alias, 0, sizeof(struct alias_st)); + + alias->name = name; + alias->target = target; + + alias->next = r->aliases; + r->aliases = alias; + } + + r->check_interval = j_atoi(config_get_one(r->config, "check.interval", 0), 60); + r->check_keepalive = j_atoi(config_get_one(r->config, "check.keepalive", 0), 0); +} + +static int _router_sx_sasl_callback(int cb, void *arg, void ** res, sx_t s, void *cbarg) { + router_t r = (router_t) cbarg; + sx_sasl_creds_t creds; + char buf[1024]; + char *pass; + + switch(cb) { + case sx_sasl_cb_GET_REALM: + strcpy(buf, "jabberd-router"); + *res = (void *)buf; + return sx_sasl_ret_OK; + break; + + case sx_sasl_cb_GET_PASS: + creds = (sx_sasl_creds_t) arg; + + log_debug(ZONE, "sx sasl callback: get pass (authnid=%s, realm=%s)", creds->authnid, creds->realm); + + pass = xhash_get(r->users, creds->authnid); + if(pass == NULL) + return sx_sasl_ret_FAIL; + + *res = (void *)pass; + return sx_sasl_ret_OK; + break; + + case sx_sasl_cb_CHECK_PASS: + creds = (sx_sasl_creds_t) arg; + + log_debug(ZONE, "sx sasl callback: check pass (authnid=%s, realm=%s)", creds->authnid, creds->realm); + + pass = xhash_get(r->users, creds->authnid); + if(pass == NULL || strcmp(creds->pass, pass) != 0) + return sx_sasl_ret_OK; + + return sx_sasl_ret_FAIL; + break; + + case sx_sasl_cb_CHECK_AUTHZID: + /* We just need to ensure that authnid == authzid, which top + * level does for us at the moment. Must revist this if this + * changes, however */ + return sx_sasl_ret_OK; + break; + + case sx_sasl_cb_CHECK_MECH: + + if (strcasecmp((char *)arg,"DIGEST-MD5")==0) + return sx_sasl_ret_OK; + + return sx_sasl_ret_FAIL; + break; + + default: + break; + } + + return 0; +} + +static void _router_time_checks(router_t r) { + component_t target; + time_t now; + union xhashv xhv; + + now = time(NULL); + + /* loop the components and distribute an space on idle connections*/ + if(xhash_iter_first(r->components)) + do { + xhv.comp_val = ⌖ + xhash_iter_get(r->components, NULL, xhv.val); + + if(r->check_keepalive > 0 && target->last_activity > 0 && now > target->last_activity + r->check_keepalive && target->s->state >= state_STREAM) { + log_debug(ZONE, "sending keepalive for %d", target->fd); + sx_raw_write(target->s, " ", 1); + } + } while(xhash_iter_next(r->components)); + return; +} + + +int main(int argc, char **argv) +{ + router_t r; + char *config_file; + int optchar; + rate_t rt; + component_t comp; + union xhashv xhv; + +#ifdef POOL_DEBUG + time_t pool_time = 0; +#endif + +#ifdef HAVE_UMASK + umask((mode_t) 0027); +#endif + + srand(time(NULL)); + +#ifdef HAVE_WINSOCK2_H +/* get winsock running */ + { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + /* !!! tell user that we couldn't find a usable winsock dll */ + return 0; + } + } +#endif + + jabber_signal(SIGINT, router_signal); + jabber_signal(SIGTERM, router_signal); +#ifdef SIGHUP + jabber_signal(SIGHUP, router_signal_hup); +#endif +#ifdef SIGPIPE + jabber_signal(SIGPIPE, SIG_IGN); +#endif + + r = (router_t) malloc(sizeof(struct router_st)); + memset(r, 0, sizeof(struct router_st)); + + /* load our config */ + r->config = config_new(); + + config_file = CONFIG_DIR "/router.xml"; + + /* cmdline parsing */ + while((optchar = getopt(argc, argv, "Dc:h?")) >= 0) + { + switch(optchar) + { + case 'c': + config_file = optarg; + break; + case 'D': +#ifdef DEBUG + set_debug_flag(1); +#else + printf("WARN: Debugging not enabled. Ignoring -D.\n"); +#endif + break; + case 'h': case '?': default: + fputs( + "router - jabberd router (" VERSION ")\n" + "Usage: router \n" + "Options are:\n" + " -c config file to use [default: " CONFIG_DIR "/router.xml]\n" +#ifdef DEBUG + " -D Show debug output\n" +#endif + , + stdout); + config_free(r->config); + free(r); + return 1; + } + } + + if(config_load(r->config, config_file) != 0) + { + fputs("router: couldn't load config, aborting\n", stderr); + config_free(r->config); + free(r); + return 2; + } + + _router_config_expand(r); + + r->log = log_new(r->log_type, r->log_ident, r->log_facility); + log_write(r->log, LOG_NOTICE, "starting up"); + + _router_pidfile(r); + + user_table_load(r); + + r->aci = aci_load(r); + + r->conn_rates = xhash_new(101); + + r->pc = prep_cache_new(); + + r->components = xhash_new(101); + r->routes = xhash_new(101); + + r->log_sinks = xhash_new(101); + + r->dead = jqueue_new(); + + r->sx_env = sx_env_new(); + +#ifdef HAVE_SSL + if(r->local_pemfile != NULL) { + r->sx_ssl = sx_env_plugin(r->sx_env, sx_ssl_init, r->local_pemfile, NULL); + if(r->sx_ssl == NULL) + log_write(r->log, LOG_ERR, "failed to load SSL pemfile, SSL disabled"); + } +#endif + + /* get sasl online */ + r->sx_sasl = sx_env_plugin(r->sx_env, sx_sasl_init, "jabberd-router", SASL_SEC_NOANONYMOUS, _router_sx_sasl_callback, (void *) r, 0); + if(r->sx_sasl == NULL) { + log_write(r->log, LOG_ERR, "failed to initialise SASL context, aborting"); + exit(1); + } + + r->mio = mio_new(r->io_max_fds); + + r->fd = mio_listen(r->mio, r->local_port, r->local_ip, router_mio_callback, (void *) r); + if(r->fd < 0) { + log_write(r->log, LOG_ERR, "[%s, port=%d] unable to listen (%s)", r->local_ip, r->local_port, strerror(errno)); + exit(1); + } + + log_write(r->log, LOG_NOTICE, "[%s, port=%d] listening for incoming connections", r->local_ip, r->local_port, strerror(errno)); + + while(!router_shutdown) + { + mio_run(r->mio, 5); + + if(router_logrotate) + { + log_write(r->log, LOG_NOTICE, "reopening log ..."); + log_free(r->log); + r->log = log_new(r->log_type, r->log_ident, r->log_facility); + log_write(r->log, LOG_NOTICE, "log started"); + + router_logrotate = 0; + } + + /* cleanup dead sx_ts */ + while(jqueue_size(r->dead) > 0) + sx_free((sx_t) jqueue_pull(r->dead)); + + /* time checks */ + if(r->check_interval > 0 && time(NULL) >= r->next_check) { + log_debug(ZONE, "running time checks"); + + _router_time_checks(r); + + r->next_check = time(NULL) + r->check_interval; + log_debug(ZONE, "next time check at %d", r->next_check); + } + +#ifdef POOL_DEBUG + if(time(NULL) > pool_time + 60) { + pool_stat(1); + pool_time = time(NULL); + } +#endif + } + + log_write(r->log, LOG_NOTICE, "shutting down"); + + /* + * !!! issue remote shutdowns to each service, so they can clean up. + * we'll need to mio_run() until they all disconnect, so that + * the the last packets (eg sm presence unavailables) can get to + * their destinations + */ + + /* close connections to components */ + xhv.comp_val = ∁ + if(xhash_iter_first(r->components)) + do { + xhash_iter_get(r->components, NULL, xhv.val); + sx_close(comp->s); + } while(xhash_count(r->components) > 0); + + xhash_free(r->components); + + /* cleanup dead sx_ts */ + while(jqueue_size(r->dead) > 0) + sx_free((sx_t) jqueue_pull(r->dead)); + + jqueue_free(r->dead); + + /* walk r->conn_rates and free */ + xhv.rt_val = &rt; + if(xhash_iter_first(r->conn_rates)) + do { + xhash_iter_get(r->conn_rates, NULL, xhv.val); + rate_free(rt); + } while(xhash_iter_next(r->conn_rates)); + + xhash_free(r->conn_rates); + + xhash_free(r->log_sinks); + + xhash_free(r->routes); + + /* unload users */ + user_table_unload(r); + + /* unload acls */ + aci_unload(r->aci); + + sx_env_free(r->sx_env); + + prep_cache_free(r->pc); + + mio_free(r->mio); + + access_free(r->access); + + log_free(r->log); + + config_free(r->config); + + free(r); + +#ifdef POOL_DEBUG + pool_stat(1); +#endif + +#ifdef HAVE_WINSOCK2_H + WSACleanup(); +#endif + + return 0; +} diff --git a/router/router.c b/router/router.c new file mode 100644 index 00000000..90058385 --- /dev/null +++ b/router/router.c @@ -0,0 +1,939 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "router.h" + +/** info for broadcasts */ +typedef struct broadcast_st { + router_t r; + component_t src; + nad_t nad; +} *broadcast_t; + +/** broadcast a packet */ +static void _router_broadcast(xht routes, const char *key, void *val, void *arg) { + broadcast_t bc = (broadcast_t) arg; + component_t comp = (component_t) val; + + /* I don't care about myself or the elderly (!?) */ + if(comp == bc->src || comp->legacy) + return; + + sx_nad_write(comp->s, nad_copy(bc->nad)); +} + +/** domain advertisement */ +static void _router_advertise(router_t r, char *domain, component_t src, int unavail) { + struct broadcast_st bc; + int ns; + + log_debug(ZONE, "advertising %s to all routes (unavail=%d)", domain, unavail); + + bc.r = r; + bc.src = src; + + /* create a new packet */ + bc.nad = nad_new(src->s->nad_cache); + ns = nad_add_namespace(bc.nad, uri_COMPONENT, NULL); + nad_append_elem(bc.nad, ns, "presence", 0); + nad_append_attr(bc.nad, -1, "from", domain); + if(unavail) + nad_append_attr(bc.nad, -1, "type", "unavailable"); + + xhash_walk(r->routes, _router_broadcast, (void *) &bc); + + nad_free(bc.nad); +} + +/** tell a component about all the others */ +static void _router_advertise_reverse(xht routes, const char *key, void *val, void *arg) { + component_t dest = (component_t) arg, comp = (component_t) val; + int ns; + nad_t nad; + + /* don't tell me about myself */ + if(comp == dest) + return; + + log_debug(ZONE, "informing component about %s", key); + + /* create a new packet */ + nad = nad_new(dest->s->nad_cache); + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, ns, "presence", 0); + nad_append_attr(nad, -1, "from", (char *) key); + + sx_nad_write(dest->s, nad); +} + +static void _router_process_handshake(component_t comp, nad_t nad) { + char *hash; + int hashlen; + + /* must have a hash as cdata */ + if(NAD_CDATA_L(nad, 0) != 40) { + log_debug(ZONE, "handshake isn't long enough to be a sha1 hash"); + sx_error(comp->s, stream_err_NOT_AUTHORIZED, "handshake isn't long enough to be a sha1 hash"); + sx_close(comp->s); + + nad_free(nad); + return; + } + + /* make room for shahash_r to work .. needs at least 41 chars */ + hashlen = strlen(comp->s->id) + strlen(comp->r->local_secret) + 1; + if(hashlen < 41) + hashlen = 41; + + /* build the creds and hash them */ + hash = (char *) malloc(sizeof(char) * hashlen); + sprintf(hash, "%s%s", comp->s->id, comp->r->local_secret); + shahash_r(hash, hash); + + /* check */ + log_debug(ZONE, "checking their hash %.*s against our hash %s", 40, NAD_CDATA(nad, 0), hash); + + if(strncmp(hash, NAD_CDATA(nad, 0), 40) == 0) { + log_debug(ZONE, "handshake succeeded"); + + free(hash); + + /* respond */ + nad->elems[0].icdata = nad->elems[0].itail = -1; + nad->elems[0].lcdata = nad->elems[0].ltail = 0; + sx_nad_write(comp->s, nad); + + sx_auth(comp->s, "handshake", comp->s->req_to); + + return; + } + + log_debug(ZONE, "auth failed"); + + free(hash); + + /* failed, let them know */ + sx_error(comp->s, stream_err_NOT_AUTHORIZED, "hash didn't match, auth failed"); + sx_close(comp->s); + + nad_free(nad); +} + +static void _router_process_bind(component_t comp, nad_t nad) { + int attr; + jid_t name; + alias_t alias; + char *user, *c; + + attr = nad_find_attr(nad, 0, -1, "name", NULL); + if(attr < 0 || (name = jid_new(comp->r->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "no or invalid 'name' on bind packet, bouncing"); + nad_set_attr(nad, 0, -1, "error", "400", 3); + sx_nad_write(comp->s, nad); + return; + } + + user = strdup(comp->s->auth_id); + c = strchr(user, '@'); + if(c != NULL) *c = '\0'; + + if(strcmp(user, name->domain) != 0 && !aci_check(comp->r->aci, "bind", user)) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] tried to bind '%s', but their username (%s) is not permitted to bind other names", comp->ip, comp->port, name->domain, user); + nad_set_attr(nad, 0, -1, "name", NULL, 0); + nad_set_attr(nad, 0, -1, "error", "403", 3); + sx_nad_write(comp->s, nad); + jid_free(name); + free(user); + return; + } + + if(xhash_get(comp->r->routes, name->domain) != NULL) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] tried to bind '%s', but it's already bound", comp->ip, comp->port, name->domain); + nad_set_attr(nad, 0, -1, "name", NULL, 0); + nad_set_attr(nad, 0, -1, "error", "409", 3); + sx_nad_write(comp->s, nad); + jid_free(name); + free(user); + return; + } + + for(alias = comp->r->aliases; alias != NULL; alias = alias->next) + if(strcmp(alias->name, name->domain) == 0) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] tried to bind '%s', but that name is aliased", comp->ip, comp->port); + nad_set_attr(nad, 0, -1, "name", NULL, 0); + nad_set_attr(nad, 0, -1, "error", "409", 3); + sx_nad_write(comp->s, nad); + jid_free(name); + free(user); + return; + } + + /* default route */ + if(nad_find_elem(nad, 0, NAD_ENS(nad, 0), "default", 1) >= 0) { + if(!aci_check(comp->r->aci, "default-route", user)) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] tried to bind '%s' as the default route, but their username (%s) is not permitted to set a default route", comp->ip, comp->port, name->domain, user); + nad_set_attr(nad, 0, -1, "name", NULL, 0); + nad_set_attr(nad, 0, -1, "error", "403", 3); + sx_nad_write(comp->s, nad); + jid_free(name); + free(user); + return; + } + + if(comp->r->default_route != NULL) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] tried to bind '%s' as the default route, but one already exists", comp->ip, comp->port, name->domain); + nad_set_attr(nad, 0, -1, "name", NULL, 0); + nad_set_attr(nad, 0, -1, "error", "409", 3); + sx_nad_write(comp->s, nad); + jid_free(name); + return; + } + + log_write(comp->r->log, LOG_NOTICE, "[%s] set as default route", name->domain); + + comp->r->default_route = strdup(name->domain); + } + + /* log sinks */ + if(nad_find_elem(nad, 0, NAD_ENS(nad, 0), "log", 1) >= 0) { + if(!aci_check(comp->r->aci, "log", user)) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] tried to bind '%s' as a log sink, but their username (%s) is not permitted to do this", comp->ip, comp->port, name->domain, user); + nad_set_attr(nad, 0, -1, "name", NULL, 0); + nad_set_attr(nad, 0, -1, "error", "403", 3); + sx_nad_write(comp->s, nad); + jid_free(name); + free(user); + return; + } + + log_write(comp->r->log, LOG_NOTICE, "[%s] set as log sink", name->domain); + + xhash_put(comp->r->log_sinks, pstrdup(xhash_pool(comp->r->log_sinks), name->domain), (void *) comp); + } + + free(user); + + xhash_put(comp->r->routes, pstrdup(xhash_pool(comp->r->routes), name->domain), (void *) comp); + xhash_put(comp->routes, pstrdup(xhash_pool(comp->routes), name->domain), (void *) comp); + + log_write(comp->r->log, LOG_NOTICE, "[%s] online (bound to %s, port %d)", name->domain, comp->ip, comp->port); + + nad_set_attr(nad, 0, -1, "name", NULL, 0); + sx_nad_write(comp->s, nad); + + /* advertise name */ + _router_advertise(comp->r, name->domain, comp, 0); + + /* tell the new component about everyone else */ + xhash_walk(comp->r->routes, _router_advertise_reverse, (void *) comp); + + /* bind aliases */ + for(alias = comp->r->aliases; alias != NULL; alias = alias->next) { + if(strcmp(alias->target, name->domain) == 0) { + xhash_put(comp->r->routes, pstrdup(xhash_pool(comp->r->routes), alias->name), (void *) comp); + xhash_put(comp->routes, pstrdup(xhash_pool(comp->r->routes), alias->name), (void *) comp); + + log_write(comp->r->log, LOG_NOTICE, "[%s] online (alias of '%s', bound to %s, port %d)", alias->name, name->domain, comp->ip, comp->port); + + /* advertise name */ + _router_advertise(comp->r, alias->name, comp, 0); + } + } + + /* done with this */ + jid_free(name); +} + +static void _router_process_unbind(component_t comp, nad_t nad) { + int attr; + jid_t name; + + attr = nad_find_attr(nad, 0, -1, "name", NULL); + if(attr < 0 || (name = jid_new(comp->r->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "no or invalid 'name' on unbind packet, bouncing"); + nad_set_attr(nad, 0, -1, "error", "400", 3); + sx_nad_write(comp->s, nad); + return; + } + + if(xhash_get(comp->routes, name->domain) == NULL) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] tried to unbind '%s', but it's not bound to this component", comp->ip, comp->port, name->domain); + nad_set_attr(nad, 0, -1, "name", NULL, 0); + nad_set_attr(nad, 0, -1, "error", "404", 3); + sx_nad_write(comp->s, nad); + jid_free(name); + return; + } + + xhash_zap(comp->r->log_sinks, name->domain); + xhash_zap(comp->r->routes, name->domain); + xhash_zap(comp->routes, name->domain); + + if(comp->r->default_route != NULL && strcmp(comp->r->default_route, name->domain) == 0) { + log_write(comp->r->log, LOG_NOTICE, "[%s] default route offline", name->domain); + free(comp->r->default_route); + comp->r->default_route = NULL; + } + + log_write(comp->r->log, LOG_NOTICE, "[%s] offline", name->domain); + + nad_set_attr(nad, 0, -1, "name", NULL, 0); + sx_nad_write(comp->s, nad); + + /* deadvertise name */ + _router_advertise(comp->r, name->domain, comp, 1); + + jid_free(name); +} + +static void _router_comp_write(component_t comp, nad_t nad) { + int attr; + + if(comp->tq != NULL) { + log_debug(ZONE, "%s port %d is throttled, jqueueing packet", comp->ip, comp->port); + jqueue_push(comp->tq, nad, 0); + return; + } + + /* packets go raw to normal components */ + if(!comp->legacy) { + sx_nad_write(comp->s, nad); + return; + } + + log_debug(ZONE, "packet for legacy component, munging"); + + attr = nad_find_attr(nad, 0, -1, "error", NULL); + if(attr >= 0) { + if(NAD_AVAL_L(nad, attr) == 3 && strncmp("400", NAD_AVAL(nad, attr), 3) == 0) + stanza_error(nad, 1, stanza_err_BAD_REQUEST); + else + stanza_error(nad, 1, stanza_err_SERVICE_UNAVAILABLE); + } + + sx_nad_write_elem(comp->s, nad, 1); +} + +static void _router_route_log_sink(xht log_sinks, const char *key, void *val, void *arg) { + component_t comp = (component_t) val; + nad_t nad = (nad_t) arg; + + log_debug(ZONE, "copying route to '%s' (%s, port %d)", key, comp->ip, comp->port); + + _router_comp_write(comp, nad_copy(nad)); +} + +static void _router_process_route(component_t comp, nad_t nad) { + int atype, ato, afrom; + struct jid_st sto, sfrom; + jid_static_buf sto_buf, sfrom_buf; + jid_t to = NULL, from = NULL; + component_t target; + union xhashv xhv; + + /* init static jid */ + jid_static(&sto,&sto_buf); + jid_static(&sfrom,&sfrom_buf); + + if(nad_find_attr(nad, 0, -1, "error", NULL) >= 0) { + log_debug(ZONE, "dropping error packet, trying to avoid loops"); + nad_free(nad); + return; + } + + atype = nad_find_attr(nad, 0, -1, "type", NULL); + ato = nad_find_attr(nad, 0, -1, "to", NULL); + afrom = nad_find_attr(nad, 0, -1, "from", NULL); + + sto.pc = sfrom.pc = comp->r->pc; + if(ato >= 0) to = jid_reset(&sto, NAD_AVAL(nad, ato), NAD_AVAL_L(nad, ato)); + if(afrom >= 0) from = jid_reset(&sfrom, NAD_AVAL(nad, afrom), NAD_AVAL_L(nad, afrom)); + + /* unicast */ + if(atype < 0) { + if(to == NULL || from == NULL) { + log_debug(ZONE, "unicast route with missing or invalid to or from, bouncing"); + nad_set_attr(nad, 0, -1, "error", "400", 3); + _router_comp_write(comp, nad); + return; + } + + log_debug(ZONE, "unicast route from %s to %s", from->domain, to->domain); + + /* check the from */ + if(xhash_get(comp->routes, from->domain) == NULL) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] tried to send a packet from '%s', but that name is not bound to this component", comp->ip, comp->port, from->domain); + nad_set_attr(nad, 0, -1, "error", "401", 3); + _router_comp_write(comp, nad); + return; + } + + /* find a target */ + target = xhash_get(comp->r->routes, to->domain); + if(target == NULL) { + if(comp->r->default_route != NULL && strcmp(from->domain, comp->r->default_route) == 0) { + log_debug(ZONE, "%s is unbound, bouncing", from->domain); + nad_set_attr(nad, 0, -1, "error", "404", 3); + _router_comp_write(comp, nad); + return; + } + target = xhash_get(comp->r->routes, comp->r->default_route); + } + + if(target == NULL) { + log_debug(ZONE, "%s is unbound, and no default route, bouncing", to->domain); + nad_set_attr(nad, 0, -1, "error", "404", 3); + _router_comp_write(comp, nad); + return; + } + + /* copy to any log sinks */ + if(xhash_count(comp->r->log_sinks) > 0) + xhash_walk(comp->r->log_sinks, _router_route_log_sink, (void *) nad); + + /* push it out */ + log_debug(ZONE, "writing route for '%s' to %s, port %d", to->domain, target->ip, target->port); + + _router_comp_write(target, nad); + + return; + } + + /* broadcast */ + if(NAD_AVAL_L(nad, atype) == 9 && strncmp("broadcast", NAD_AVAL(nad, atype), 9) == 0) { + if(from == NULL) { + log_debug(ZONE, "broadcast route with missing or invalid from, bouncing"); + nad_set_attr(nad, 0, -1, "error", "400", 3); + _router_comp_write(comp, nad); + return; + } + + log_debug(ZONE, "broadcast route from %s", from->domain); + + /* check the from */ + if(xhash_get(comp->routes, from->domain) == NULL) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] tried to send a packet from '%s', but that name is not bound to this component", comp->ip, comp->port, from->domain); + nad_set_attr(nad, 0, -1, "error", "401", 3); + _router_comp_write(comp, nad); + return; + } + + /* loop the components and distribute */ + if(xhash_iter_first(comp->r->components)) + do { + xhv.comp_val = ⌖ + xhash_iter_get(comp->r->components, NULL, xhv.val); + + if(target != comp) { + log_debug(ZONE, "writing broadcast to %s, port %d", target->ip, target->port); + + _router_comp_write(target, nad_copy(nad)); + } + } while(xhash_iter_next(comp->r->components)); + + nad_free(nad); + + return; + } + + log_debug(ZONE, "unknown route type '%.*s', dropping", NAD_AVAL_L(nad, atype), NAD_AVAL(nad, atype)); + + nad_free(nad); +} + +static void _router_process_throttle(component_t comp, nad_t nad) { + jqueue_t tq; + nad_t pkt; + + if(comp->tq == NULL) { + _router_comp_write(comp, nad); + + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] throttling packets on request", comp->ip, comp->port); + comp->tq = jqueue_new(); + } + + else { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] unthrottling packets on request", comp->ip, comp->port); + tq = comp->tq; + comp->tq = NULL; + + _router_comp_write(comp, nad); + + while((pkt = jqueue_pull(tq)) != NULL) + _router_comp_write(comp, pkt); + + jqueue_free(tq); + } +} + +static int _router_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + component_t comp = (component_t) arg; + sx_buf_t buf = (sx_buf_t) data; + int rlen, len, attr, ns, sns; + sx_error_t *sxe; + nad_t nad; + struct jid_st sto, sfrom; + jid_static_buf sto_buf, sfrom_buf; + jid_t to, from; + alias_t alias; + + /* init static jid */ + jid_static(&sto,&sto_buf); + jid_static(&sfrom,&sfrom_buf); + + switch(e) { + case event_WANT_READ: + log_debug(ZONE, "want read"); + mio_read(comp->r->mio, comp->fd); + break; + + case event_WANT_WRITE: + log_debug(ZONE, "want write"); + mio_write(comp->r->mio, comp->fd); + break; + + case event_READ: + log_debug(ZONE, "reading from %d", comp->fd); + + /* check rate limits */ + if(comp->rate != NULL) { + if(rate_check(comp->rate) == 0) { + + /* inform the app if we haven't already */ + if(!comp->rate_log) { + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] is being byte rate limited", comp->ip, comp->port); + + comp->rate_log = 1; + } + + log_debug(ZONE, "%d is throttled, delaying read", comp->fd); + + buf->len = 0; + return 0; + } + + /* find out how much we can have */ + rlen = rate_left(comp->rate); + if(rlen > buf->len) + rlen = buf->len; + } + + /* no limit, just read as much as we can */ + else + rlen = buf->len; + + /* do the read */ + len = recv(comp->fd, buf->data, rlen, 0); + + /* update rate limits */ + if(comp->rate != NULL && len > 0) { + comp->rate_log = 0; + rate_add(comp->rate, len); + } + + if(len < 0) { + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) { + buf->len = 0; + return 0; + } + + log_debug(ZONE, "read failed: %s", strerror(errno)); + + sx_kill(comp->s); + + return -1; + } + + else if(len == 0) { + /* they went away */ + sx_kill(comp->s); + + return -1; + } + + log_debug(ZONE, "read %d bytes", len); + + buf->len = len; + + return len; + + case event_WRITE: + log_debug(ZONE, "writing to %d", comp->fd); + + len = send(comp->fd, buf->data, buf->len, 0); + if(len >= 0) { + log_debug(ZONE, "%d bytes written", len); + return len; + } + + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) + return 0; + + log_debug(ZONE, "write failed: %s", strerror(errno)); + + sx_kill(comp->s); + + return -1; + + case event_ERROR: + sxe = (sx_error_t *) data; + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] error: %s (%s)", comp->ip, comp->port, sxe->generic, sxe->specific); + + break; + + case event_STREAM: + + /* legacy check */ + if(s->ns == NULL || strcmp("jabber:component:accept", s->ns) != 0) + return 0; + + /* component, old skool */ + comp->legacy = 1; + + /* enabled? */ + if(comp->r->local_secret == NULL) { + sx_error(s, stream_err_INVALID_NAMESPACE, "support for legacy components not available"); /* !!! correct error? */ + sx_close(s); + return 0; + } + + /* sanity */ + if(s->req_to == NULL) { + sx_error(s, stream_err_HOST_UNKNOWN, "no 'to' attribute on stream header"); + sx_close(s); + return 0; + } + + break; + + case event_OPEN: + + log_write(comp->r->log, LOG_NOTICE, "[%s, port=%d] authenticated as %s", comp->ip, comp->port, comp->s->auth_id); + + /* make a route for legacy components */ + if(comp->legacy) { + /* make sure the name is available */ + if(xhash_get(comp->r->routes, s->req_to) != NULL) { + sx_error(s, stream_err_HOST_UNKNOWN, "requested name is already in use"); /* !!! correct error? */ + sx_close(s); + return 0; + } + + for(alias = comp->r->aliases; alias != NULL; alias = alias->next) + if(strcmp(alias->name, s->req_to) == 0) { + sx_error(s, stream_err_HOST_UNKNOWN, "requested name is already in use"); /* !!! correct error? */ + sx_close(s); + return 0; + } + + + xhash_put(comp->r->routes, pstrdup(xhash_pool(comp->r->routes), s->req_to), (void *) comp); + xhash_put(comp->routes, pstrdup(xhash_pool(comp->routes), s->req_to), (void *) comp); + + log_write(comp->r->log, LOG_NOTICE, "[%s] online (bound to %s, port %d)", s->req_to, comp->ip, comp->port); + + /* advertise the name */ + _router_advertise(comp->r, s->req_to, comp, 0); + + /* this is a legacy component, so we don't tell it about other routes */ + + /* bind aliases */ + for(alias = comp->r->aliases; alias != NULL; alias = alias->next) { + if(strcmp(alias->target, s->req_to) == 0) { + xhash_put(comp->r->routes, pstrdup(xhash_pool(comp->r->routes), alias->name), (void *) comp); + xhash_put(comp->routes, pstrdup(xhash_pool(comp->r->routes), alias->name), (void *) comp); + + log_write(comp->r->log, LOG_NOTICE, "[%s] online (alias of '%s', bound to %s, port %d)", alias->name, s->req_to, comp->ip, comp->port); + + /* advertise name */ + _router_advertise(comp->r, alias->name, comp, 0); + } + } + } + + break; + + case event_PACKET: + nad = (nad_t) data; + + /* preauth */ + if(comp->s->state == state_STREAM) { + /* non-legacy components can't do anything before auth */ + if(!comp->legacy) { + log_debug(ZONE, "stream is preauth, dropping packet"); + nad_free(nad); + return 0; + } + + /* watch for handshake requests */ + if(NAD_ENAME_L(nad, 0) != 9 || strncmp("handshake", NAD_ENAME(nad, 0), NAD_ENAME_L(nad, 0)) != 0) { + log_debug(ZONE, "unknown preauth packet %.*s, dropping", NAD_ENAME_L(nad, 0), NAD_ENAME(nad, 0)); + + nad_free(nad); + return 0; + } + + /* process incoming handshakes */ + _router_process_handshake(comp, nad); + + return 0; + } + + /* legacy processing */ + if(comp->legacy) { + log_debug(ZONE, "packet from legacy component, munging it"); + + sto.pc = sfrom.pc = comp->r->pc; + + attr = nad_find_attr(nad, 0, -1, "to", NULL); + if(attr < 0 || (to = jid_reset(&sto, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "invalid or missing 'to' address on legacy packet, dropping it"); + nad_free(nad); + return 0; + } + + attr = nad_find_attr(nad, 0, -1, "from", NULL); + if(attr < 0 || (from = jid_reset(&sfrom, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "invalid or missing 'from' address on legacy packet, dropping it"); + nad_free(nad); + return 0; + } + + /* rewrite component packets into client packets */ + ns = nad_find_namespace(nad, 0, "jabber:component:accept", NULL); + if(ns >= 0) { + if(nad->elems[0].ns == ns) + nad->elems[0].ns = nad->nss[nad->elems[0].ns].next; + else { + for(sns = nad->elems[0].ns; sns >= 0 && nad->nss[sns].next != ns; sns = nad->nss[sns].next); + nad->nss[sns].next = nad->nss[nad->nss[sns].next].next; + } + } + + ns = nad_find_namespace(nad, 0, uri_CLIENT, NULL); + if(ns < 0) { + ns = nad_add_namespace(nad, uri_CLIENT, NULL); + nad->scope = -1; + nad->nss[ns].next = nad->elems[0].ns; + nad->elems[0].ns = ns; + } + nad->elems[0].my_ns = ns; + + /* wrap up the packet */ + ns = nad_add_namespace(nad, uri_COMPONENT, "comp"); + + nad_wrap_elem(nad, 0, ns, "route"); + + nad_set_attr(nad, 0, -1, "to", to->domain, 0); + nad_set_attr(nad, 0, -1, "from", from->domain, 0); + } + + /* top element must be router scoped */ + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_COMPONENT) || strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) != 0) { + log_debug(ZONE, "invalid packet namespace, dropping"); + nad_free(nad); + return 0; + } + + /* bind a name to this component */ + if(NAD_ENAME_L(nad, 0) == 4 && strncmp("bind", NAD_ENAME(nad, 0), 4) == 0) { + _router_process_bind(comp, nad); + return 0; + } + + /* unbind a name from this component */ + if(NAD_ENAME_L(nad, 0) == 6 && strncmp("unbind", NAD_ENAME(nad, 0), 6) == 0) { + _router_process_unbind(comp, nad); + return 0; + } + + /* route packets */ + if(NAD_ENAME_L(nad, 0) == 5 && strncmp("route", NAD_ENAME(nad, 0), 5) == 0) { + _router_process_route(comp, nad); + return 0; + } + + /* throttle packets */ + if(NAD_ENAME_L(nad, 0) == 8 && strncmp("throttle", NAD_ENAME(nad, 0), 8) == 0) { + _router_process_throttle(comp, nad); + return 0; + } + + log_debug(ZONE, "unknown packet, dropping"); + + nad_free(nad); + return 0; + + case event_CLOSED: + mio_close(comp->r->mio, comp->fd); + + break; + } + + return 0; +} + +static int _router_accept_check(router_t r, int fd, char *ip) { + rate_t rt; + + if(access_check(r->access, ip) == 0) { + log_write(r->log, LOG_NOTICE, "[%d] [%s] access denied by configuration", fd, ip); + return 1; + } + + if(r->conn_rate_total != 0) { + rt = (rate_t) xhash_get(r->conn_rates, ip); + if(rt == NULL) { + rt = rate_new(r->conn_rate_total, r->conn_rate_seconds, r->conn_rate_wait); + xhash_put(r->conn_rates, pstrdup(xhash_pool(r->conn_rates), ip), (void *) rt); + } + + if(rate_check(rt) == 0) { + log_write(r->log, LOG_NOTICE, "[%d] [%s] is being rate limited", fd, ip); + return 1; + } + + rate_add(rt, 1); + } + + return 0; +} + +static void _router_route_unbind_walker(xht routes, const char *key, void *val, void *arg) { + component_t comp = (component_t) arg; + + xhash_zap(comp->r->log_sinks, key); + xhash_zap(comp->r->routes, key); + xhash_zap(comp->routes, key); + + if(comp->r->default_route != NULL && strcmp(key, comp->r->default_route) == 0) { + log_write(comp->r->log, LOG_NOTICE, "[%s] default route offline", key); + free(comp->r->default_route); + comp->r->default_route = NULL; + } + + log_write(comp->r->log, LOG_NOTICE, "[%s] offline", key); + + /* deadvertise name */ + _router_advertise(comp->r, (char *) key, comp, 1); +} + +int router_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) { + component_t comp = (component_t) arg; + router_t r = (router_t) arg; + struct sockaddr_storage sa; + int namelen = sizeof(sa), port, nbytes; + + switch(a) { + case action_READ: + log_debug(ZONE, "read action on fd %d", fd); + + /* they did something */ + comp->last_activity = time(NULL); + + ioctl(fd, FIONREAD, &nbytes); + if(nbytes == 0) { + sx_kill(comp->s); + return 0; + } + + return sx_can_read(comp->s); + + case action_WRITE: + log_debug(ZONE, "write action on fd %d", fd); + + /* update activity timestamp */ + comp->last_activity = time(NULL); + + return sx_can_write(comp->s); + + case action_CLOSE: + log_debug(ZONE, "close action on fd %d", fd); + + r = comp->r; + + log_write(r->log, LOG_NOTICE, "[%s, port=%d] disconnect", comp->ip, comp->port); + + /* unbind names */ + xhash_walk(comp->routes, _router_route_unbind_walker, (void *) comp); + + /* deregister component */ + xhash_zap(r->components, comp->ipport); + + xhash_free(comp->routes); + + if(comp->tq != NULL) + /* !!! bounce packets */ + jqueue_free(comp->tq); + + rate_free(comp->rate); + + jqueue_push(comp->r->dead, (void *) comp->s, 0); + + free(comp); + + break; + + case action_ACCEPT: + log_debug(ZONE, "accept action on fd %d", fd); + + getpeername(fd, (struct sockaddr *) &sa, &namelen); + port = j_inet_getport(&sa); + + log_write(r->log, LOG_NOTICE, "[%s, port=%d] connect", (char *) data, port); + + if(_router_accept_check(r, fd, (char *) data) != 0) + return 1; + + comp = (component_t) malloc(sizeof(struct component_st)); + memset(comp, 0, sizeof(struct component_st)); + + comp->r = r; + + comp->fd = fd; + + snprintf(comp->ip, INET6_ADDRSTRLEN, "%s", (char *) data); + comp->port = port; + + snprintf(comp->ipport, INET6_ADDRSTRLEN, "%s:%d", comp->ip, comp->port); + + comp->s = sx_new(r->sx_env, fd, _router_sx_callback, (void *) comp); + mio_app(m, fd, router_mio_callback, (void *) comp); + + if(r->byte_rate_total != 0) + comp->rate = rate_new(r->byte_rate_total, r->byte_rate_seconds, r->byte_rate_wait); + + comp->routes = xhash_new(51); + + /* register component */ + xhash_put(r->components, comp->ipport, (void *) comp); + +#ifdef HAVE_SSL + sx_server_init(comp->s, SX_SSL_STARTTLS_OFFER | SX_SASL_OFFER); +#else + sx_server_init(comp->s, SX_SASL_OFFER); +#endif + + break; + } + + return 0; +} + diff --git a/router/router.h b/router/router.h new file mode 100644 index 00000000..b4bc3e46 --- /dev/null +++ b/router/router.h @@ -0,0 +1,200 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/*! \mainpage jabberd - Jabber Open Source Server + * + * \section intro Introduction + * + * The jabberd project aims to provide an open-source server + * implementation of the Jabber protocols for instant messaging + * and XML routing. The goal of this project is to provide a + * scalable, reliable, efficient and extensible server that + * provides a complete set of features and is up to date with + * the latest protocol revisions. + * + * The project web page:\n + * http://jabberd.jabberstudio.org/ + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "sx/sx.h" +#include "sx/ssl.h" +#include "sx/sasl.h" +#include "mio/mio.h" +#include "util/util.h" + +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif + +typedef struct router_st *router_t; +typedef struct component_st *component_t; +typedef struct alias_st *alias_t; + +struct router_st { + /** our id */ + char *id; + + /** config */ + config_t config; + + /** user table */ + xht users; + time_t users_load; + + /** logging */ + log_t log; + + /** log data */ + log_type_t log_type; + char *log_facility; + char *log_ident; + + /** how we listen for stuff */ + char *local_ip; + int local_port; + char *local_secret; + char *local_pemfile; + + /** max file descriptors */ + int io_max_fds; + + /** access controls */ + access_t access; + + /** connection rates */ + int conn_rate_total; + int conn_rate_seconds; + int conn_rate_wait; + + xht conn_rates; + + /** default byte rates (karma) */ + int byte_rate_total; + int byte_rate_seconds; + int byte_rate_wait; + + /** sx environment */ + sx_env_t sx_env; + sx_plugin_t sx_ssl; + sx_plugin_t sx_sasl; + + /** managed io */ + mio_t mio; + + /** listening socket */ + int fd; + + /** time checks */ + int check_interval; + int check_keepalive; + + time_t next_check; + + /** stringprep cache */ + prep_cache_t pc; + + /** attached components, key is 'ip:port', var is component_t */ + xht components; + + /** valid routes, key is route name (packet "to" address), var is component_t */ + xht routes; + + /** default route, only one */ + char *default_route; + + /** log sinks, key is route name, var is component_t */ + xht log_sinks; + + /** configured aliases */ + alias_t aliases; + + /** access control lists */ + xht aci; + + /** list of sx_t waiting to be cleaned up */ + jqueue_t dead; +}; + +/** a single component */ +struct component_st { + router_t r; + + /** file descriptor */ + int fd; + + /** remote ip and port */ + char ip[INET6_ADDRSTRLEN]; + int port; + + /** ip:port pair */ + char ipport[INET6_ADDRSTRLEN + 6]; + + /** our stream */ + sx_t s; + + /** rate limits */ + rate_t rate; + int rate_log; + + /** valid routes to this component, key is route name */ + xht routes; + + /** true if this is an old component:accept stream */ + int legacy; + + /** throttle queue */ + jqueue_t tq; + + /** timestamps for idle timeouts */ + time_t last_activity; +}; + +struct alias_st { + char *name; + char *target; + + alias_t next; +}; + +int router_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg); +void router_sx_handshake(sx_t s, sx_buf_t buf, void *arg); + +xht aci_load(router_t r); +void aci_unload(xht aci); +int aci_check(xht acls, char *type, char *name); + +int user_table_load(router_t r); +void user_table_unload(router_t r); + +/* union for xhash_iter_get to comply with strict-alias rules for gcc3 */ +union xhashv +{ + void **val; + char **char_val; + component_t *comp_val; + rate_t *rt_val; +}; diff --git a/router/user.c b/router/user.c new file mode 100644 index 00000000..f1501f72 --- /dev/null +++ b/router/user.c @@ -0,0 +1,114 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "router.h" + +/** user table manager */ + +int user_table_load(router_t r) { + char *userfile; + FILE *f; + long size; + char *buf; + nad_cache_t cache; + nad_t nad; + int nusers, user, name, secret; + + log_debug(ZONE, "loading user table"); + + if(r->users != NULL) + xhash_free(r->users); + + r->users = xhash_new(51); + + userfile = config_get_one(r->config, "local.users", 0); + if(userfile == NULL) + userfile = CONFIG_DIR "/router-users.xml"; + + f = fopen(userfile, "r"); + if(f == NULL) { + log_write(r->log, LOG_ERR, "couldn't open user table file %s: %s", userfile, strerror(errno)); + return 1; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + + buf = (char *) malloc(sizeof(char) * size); + + fread(buf, 1, size, f); + if(ferror(f)) { + log_write(r->log, LOG_ERR, "couldn't read from user table file: %s", strerror(errno)); + free(buf); + fclose(f); + return 1; + } + + fclose(f); + + cache = nad_cache_new(); + nad = nad_parse(cache, buf, size); + if(nad == NULL) { + log_write(r->log, LOG_ERR, "couldn't parse user table"); + free(buf); + nad_cache_free(cache); + return 1; + } + + free(buf); + + nusers = 0; + user = nad_find_elem(nad, 0, -1, "user", 1); + while(user >= 0) { + name = nad_find_elem(nad, user, -1, "name", 1); + secret = nad_find_elem(nad, user, -1, "secret", 1); + + if(name < 0 || secret < 0 || NAD_CDATA_L(nad, name) <= 0 || NAD_CDATA_L(nad, secret) <= 0) { + log_write(r->log, LOG_ERR, "malformed user entry in user table file, skipping"); + continue; + } + + log_debug(ZONE, "remembering user '%.*s'", NAD_CDATA_L(nad, name), NAD_CDATA(nad, name)); + + xhash_put(r->users, pstrdupx(xhash_pool(r->users), NAD_CDATA(nad, name), NAD_CDATA_L(nad, name)), pstrdupx(xhash_pool(r->users), NAD_CDATA(nad, secret), NAD_CDATA_L(nad, secret))); + + nusers++; + + user = nad_find_elem(nad, user, -1, "user", 0); + } + + nad_free(nad); + nad_cache_free(cache); + + log_write(r->log, LOG_NOTICE, "loaded user table (%d users)", nusers); + + r->users_load = time(NULL); + + return 0; +} + +void user_table_unload(router_t r) { + + if(r->users != NULL) + xhash_free(r->users); + + return; +} diff --git a/s2s/.cvsignore b/s2s/.cvsignore new file mode 100644 index 00000000..82450b16 --- /dev/null +++ b/s2s/.cvsignore @@ -0,0 +1,5 @@ +Makefile +Makefile.in +.deps +.libs +s2s diff --git a/s2s/Makefile.am b/s2s/Makefile.am new file mode 100644 index 00000000..1b194b01 --- /dev/null +++ b/s2s/Makefile.am @@ -0,0 +1,12 @@ +INCLUDES = -DCONFIG_DIR=\"$(sysconfdir)\" + +bin_PROGRAMS = s2s + +noinst_HEADERS = s2s.h +s2s_SOURCES = in.c main.c out.c router.c sx.c util.c + +s2s_LDADD = $(top_builddir)/sx/libsx.la \ + $(top_builddir)/mio/libmio.la \ + $(top_builddir)/util/libutil.la \ + $(top_builddir)/subst/libsubst.la \ + $(top_builddir)/expat/libexpat.la diff --git a/s2s/in.c b/s2s/in.c new file mode 100644 index 00000000..75463aa1 --- /dev/null +++ b/s2s/in.c @@ -0,0 +1,570 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "s2s.h" + +/* + * we handle incoming connections, and the packets that arrive on them. + * + * action points: + * + * event_STREAM - new incoming connection + * - create new dbconn (key stream id) + * - DONE + * + * event_PACKET: key - auth request + * - get dbconn for this sx + * - if dbconn state is valid + * - send result: + * - DONE + * - out_packet(s2s, key) + * - DONE + * + * event_PACKET: key - validate their key + * - generate dbkey: sha1(secret+remote+id) + * - if their key matches dbkey + * - send them: + * - else + * - send them: + * - DONE + * + * event_PACKET - they're trying to send us something + * - get dbconn for this sx + * - if dbconn state is invalid + * - drop packet + * - DONE + * - write packet to router + * - DONE + */ + +/* forward decls */ +static int _in_sx_callback(sx_t s, sx_event_t e, void *data, void *arg); +static void _in_result(conn_t in, nad_t nad); +static void _in_verify(conn_t in, nad_t nad); +static void _in_packet(conn_t in, nad_t nad); + +int in_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) { + conn_t in = (conn_t) arg; + s2s_t s2s = (s2s_t) arg; + struct sockaddr_storage sa; + int namelen = sizeof(sa), port, nbytes; + char ipport[INET6_ADDRSTRLEN + 17]; + + switch(a) { + case action_READ: + log_debug(ZONE, "read action on fd %d", fd); + + ioctl(fd, FIONREAD, &nbytes); + if(nbytes == 0) { + sx_kill(in->s); + return 0; + } + + return sx_can_read(in->s); + + case action_WRITE: + log_debug(ZONE, "write action on fd %d", fd); + return sx_can_write(in->s); + + case action_CLOSE: + log_debug(ZONE, "close action on fd %d", fd); + + /* !!! logging */ + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] disconnect", fd, in->ip, in->port); + + jqueue_push(in->s2s->dead, (void *) in->s, 0); + + /* remove from open streams hash if online, or open connections if not */ + if (in->online) + xhash_zap(in->s2s->in, in->key); + else { + snprintf(ipport, INET6_ADDRSTRLEN + 16, "%s/%d", in->ip, in->port); + xhash_zap(in->s2s->in_accept, ipport); + } + + jqueue_push(in->s2s->dead_conn, (void *) in, 0); + + break; + + case action_ACCEPT: + s2s = (s2s_t) arg; + + log_debug(ZONE, "accept action on fd %d", fd); + + getpeername(fd, (struct sockaddr *) &sa, &namelen); + port = j_inet_getport(&sa); + + log_write(s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] incoming connection", fd, (char *) data, port); + + /* new conn */ + in = (conn_t) malloc(sizeof(struct conn_st)); + memset(in, 0, sizeof(struct conn_st)); + + in->s2s = s2s; + + strcpy(in->ip, (char *) data); + in->port = port; + + in->states = xhash_new(101); + in->states_time = xhash_new(101); + + in->fd = fd; + + in->init_time = time(NULL); + + in->s = sx_new(s2s->sx_env, in->fd, _in_sx_callback, (void *) in); + mio_app(m, in->fd, in_mio_callback, (void *) in); + + /* add to incoming connections hash */ + snprintf(ipport, INET6_ADDRSTRLEN + 16, "%s/%d", in->ip, in->port); + xhash_put(s2s->in_accept, pstrdup(xhash_pool(s2s->in_accept),ipport), (void *) in); + +#ifdef HAVE_SSL + sx_server_init(in->s, S2S_DB_HEADER | ((s2s->local_pemfile != NULL) ? SX_SSL_STARTTLS_OFFER : 0) ); +#else + sx_server_init(in->s, S2S_DB_HEADER); +#endif + break; + } + + return 0; +} + +static int _in_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + conn_t in = (conn_t) arg; + sx_buf_t buf = (sx_buf_t) data; + int len; + sx_error_t *sxe; + nad_t nad; + char ipport[INET6_ADDRSTRLEN + 17]; + + switch(e) { + case event_WANT_READ: + log_debug(ZONE, "want read"); + mio_read(in->s2s->mio, in->fd); + break; + + case event_WANT_WRITE: + log_debug(ZONE, "want write"); + mio_write(in->s2s->mio, in->fd); + break; + + case event_READ: + log_debug(ZONE, "reading from %d", in->fd); + + /* do the read */ + len = recv(in->fd, buf->data, buf->len, 0); + + if(len < 0) { + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) { + buf->len = 0; + return 0; + } + + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] read error: %s (%d)", in->fd, in->ip, in->port, strerror(errno), errno); + + sx_kill(s); + + return -1; + } + + else if(len == 0) { + /* they went away */ + sx_kill(s); + + return -1; + } + + log_debug(ZONE, "read %d bytes", len); + + buf->len = len; + + return len; + + case event_WRITE: + log_debug(ZONE, "writing to %d", in->fd); + + len = send(in->fd, buf->data, buf->len, 0); + if(len >= 0) { + log_debug(ZONE, "%d bytes written", len); + return len; + } + + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) + return 0; + + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] write error: %s (%d)", in->fd, in->ip, in->port, strerror(errno), errno); + + sx_kill(s); + + return -1; + + case event_ERROR: + sxe = (sx_error_t *) data; + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] error: %s (%s)", in->fd, in->ip, in->port, sxe->generic, sxe->specific); + + break; + + case event_STREAM: + case event_OPEN: + + log_debug(ZONE, "STREAM or OPEN event from %s port %d (id %s)", in->ip, in->port, s->id); + + /* first time, bring them online */ + if ((!in->online)||(strcmp(in->key,s->id)!=0)) { + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] incoming stream online (id %s)", in->fd, in->ip, in->port, s->id); + + in->online = 1; + + /* record the id */ + if (in->key != NULL) { + log_debug(ZONE,"adding new SSL stream id %s for stream id %s", s->id, in->key); + + /* remove the initial (non-SSL) stream id from the in connections hash */ + xhash_zap(in->s2s->in, in->key); + } + + in->key = strdup(s->id); + + /* track it - add to open streams hash and remove from new connections hash */ + xhash_put(in->s2s->in, in->key, (void *) in); + + snprintf(ipport, INET6_ADDRSTRLEN + 16, "%s/%d", in->ip, in->port); + xhash_zap(in->s2s->in_accept, ipport); + } + + break; + + case event_PACKET: + nad = (nad_t) data; + + /* update last packet timestamp */ + in->last_packet = time(NULL); + + /* dialback packets */ + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) == strlen(uri_DIALBACK) && strncmp(uri_DIALBACK, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_DIALBACK)) == 0) { + /* only result and verify mean anything */ + if(NAD_ENAME_L(nad, 0) == 6) { + if(strncmp("result", NAD_ENAME(nad, 0), 6) == 0) { + _in_result(in, nad); + return 0; + } + + if(strncmp("verify", NAD_ENAME(nad, 0), 6) == 0) { + _in_verify(in, nad); + return 0; + } + } + + log_debug(ZONE, "unknown dialback packet, dropping it"); + + nad_free(nad); + return 0; + } + + /* + * not dialback, so it has to be a normal-ish jabber packet: + * - jabber:client or jabber:server + * - message, presence or iq + * - has to and from attributes + */ + + if(!( + /* must be jabber:client or jabber:server */ + NAD_ENS(nad, 0) >= 0 && + ((NAD_NURI_L(nad, NAD_ENS(nad, 0)) == strlen(uri_CLIENT) && strncmp(uri_CLIENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_CLIENT)) == 0) || + (NAD_NURI_L(nad, NAD_ENS(nad, 0)) == strlen(uri_SERVER) && strncmp(uri_SERVER, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_SERVER)) == 0)) && ( + /* can be message */ + (NAD_ENAME_L(nad, 0) == 7 && strncmp("message", NAD_ENAME(nad, 0), 7) == 0) || + /* or presence */ + (NAD_ENAME_L(nad, 0) == 8 && strncmp("presence", NAD_ENAME(nad, 0), 8) == 0) || + /* or iq */ + (NAD_ENAME_L(nad, 0) == 2 && strncmp("iq", NAD_ENAME(nad, 0), 2) == 0) + ) && + /* to and from required */ + nad_find_attr(nad, 0, -1, "to", NULL) >= 0 && nad_find_attr(nad, 0, -1, "from", NULL) >= 0 + )) { + log_debug(ZONE, "they sent us a non-jabber looking packet, dropping it"); + nad_free(nad); + return 0; + } + + _in_packet(in, nad); + return 0; + + case event_CLOSED: + mio_close(in->s2s->mio, in->fd); + + break; + } + + return 0; +} + +/** auth requests */ +static void _in_result(conn_t in, nad_t nad) { + int attr, ns; + jid_t from, to; + char *rkey; + nad_t verify; + pkt_t pkt; + time_t now; + + attr = nad_find_attr(nad, 0, -1, "from", NULL); + if(attr < 0 || (from = jid_new(in->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid from on db result packet"); + nad_free(nad); + return; + } + + attr = nad_find_attr(nad, 0, -1, "to", NULL); + if(attr < 0 || (to = jid_new(in->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid to on db result packet"); + jid_free(from); + nad_free(nad); + return; + } + + rkey = s2s_route_key(NULL, to->domain, from->domain); + + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] received dialback auth request for route '%s'", in->fd, in->ip, in->port, rkey); + + /* get current state */ + if((conn_state_t) xhash_get(in->states, rkey) == conn_VALID) { + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] route '%s' is already valid: sending valid", in->fd, in->ip, in->port, rkey); + + /* its already valid, just reply right now */ + stanza_tofrom(nad, 0); + nad_set_attr(nad, 0, -1, "type", "valid", 5); + nad->elems[0].icdata = nad->elems[0].itail = -1; + nad->elems[0].lcdata = nad->elems[0].ltail = 0; + + sx_nad_write(in->s, nad); + + free(rkey); + + jid_free(from); + jid_free(to); + + return; + } + + /* not valid, so we need to verify */ + + /* need the key */ + if(NAD_CDATA_L(nad, 0) <= 0) { + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] no dialback key given with db result packet", in->fd, in->ip, in->port, rkey); + free(rkey); + nad_free(nad); + jid_free(from); + jid_free(to); + return; + } + + log_debug(ZONE, "requesting verification for route %s", rkey); + + /* set the route status to INPROGRESS and set timestamp */ + xhash_put(in->states, pstrdup(xhash_pool(in->states), rkey), (void *) conn_INPROGRESS); + + /* record the time that we set conn_INPROGRESS state */ + now = time(NULL); + xhash_put(in->states_time, pstrdup(xhash_pool(in->states_time), rkey), (void *) now); + + free(rkey); + + /* new packet */ + verify = nad_new(in->s2s->router->nad_cache); + ns = nad_add_namespace(verify, uri_DIALBACK, "db"); + + nad_append_elem(verify, ns, "verify", 0); + nad_append_attr(verify, -1, "to", from->domain); + nad_append_attr(verify, -1, "from", to->domain); + nad_append_attr(verify, -1, "id", in->s->id); + nad_append_cdata(verify, NAD_CDATA(nad, 0), NAD_CDATA_L(nad, 0), 1); + + /* new packet */ + pkt = (pkt_t) malloc(sizeof(struct pkt_st)); + memset(pkt, 0, sizeof(struct pkt_st)); + + pkt->nad = verify; + + pkt->to = from; + pkt->from = to; + + pkt->db = 1; + + /* its away */ + out_packet(in->s2s, pkt); + + nad_free(nad); +} + +/** validate their key */ +static void _in_verify(conn_t in, nad_t nad) { + int attr; + jid_t from, to; + char *id, *dbkey, *type; + + attr = nad_find_attr(nad, 0, -1, "from", NULL); + if(attr < 0 || (from = jid_new(in->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid from on db verify packet"); + nad_free(nad); + return; + } + + attr = nad_find_attr(nad, 0, -1, "to", NULL); + if(attr < 0 || (to = jid_new(in->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid to on db verify packet"); + jid_free(from); + nad_free(nad); + return; + } + + attr = nad_find_attr(nad, 0, -1, "id", NULL); + if(attr < 0) { + log_debug(ZONE, "missing id on db verify packet"); + jid_free(from); + jid_free(to); + nad_free(nad); + return; + } + + if(NAD_CDATA_L(nad, 0) <= 0) { + log_debug(ZONE, "no cdata on db verify packet"); + jid_free(from); + jid_free(to); + nad_free(nad); + return; + } + + /* extract the id */ + id = (char *) malloc(sizeof(char) * (NAD_AVAL_L(nad, attr) + 1)); + snprintf(id, NAD_AVAL_L(nad, attr) + 1, "%.*s", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + + /* generate a dialback key */ + dbkey = s2s_db_key(NULL, in->s2s->local_secret, from->domain, id); + + /* valid */ + if(NAD_CDATA_L(nad, 0) == strlen(dbkey) && strncmp(dbkey, NAD_CDATA(nad, 0), NAD_CDATA_L(nad, 0)) == 0) { + log_debug(ZONE, "valid dialback key %s, verify succeeded", dbkey); + type = "valid"; + } else { + log_debug(ZONE, "invalid dialback key %s, verify failed", dbkey); + type = "invalid"; + } + + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] checking dialback verification from %s: sending %s", in->fd, in->ip, in->port, from->domain, type); + + log_debug(ZONE, "letting them know"); + + /* now munge the packet and send it back to them */ + stanza_tofrom(nad, 0); + nad_set_attr(nad, 0, -1, "type", type, 0); + nad->elems[0].icdata = nad->elems[0].itail = -1; + nad->elems[0].lcdata = nad->elems[0].ltail = 0; + + sx_nad_write(in->s, nad); + + free(dbkey); + free(id); + + jid_free(from); + jid_free(to); + + return; +} + +/** they're trying to send us something */ +static void _in_packet(conn_t in, nad_t nad) { + int attr, ns, sns; + jid_t from, to; + char *rkey; + + attr = nad_find_attr(nad, 0, -1, "from", NULL); + if(attr < 0 || (from = jid_new(in->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid from on incoming packet"); + nad_free(nad); + return; + } + + attr = nad_find_attr(nad, 0, -1, "to", NULL); + if(attr < 0 || (to = jid_new(in->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid to on incoming packet"); + jid_free(from); + nad_free(nad); + return; + } + + rkey = s2s_route_key(NULL, to->domain, from->domain); + + log_debug(ZONE, "received packet from %s for %s", in->key, rkey); + + /* drop packets received on routes not valid on that connection as per XMPP 8.3.10 */ + if((conn_state_t) xhash_get(in->states, rkey) != conn_VALID) { + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] dropping packet on unvalidated route: '%s'", in->fd, in->ip, in->port, rkey); + free(rkey); + nad_free(nad); + jid_free(from); + jid_free(to); + return; + } + + free(rkey); + + /* its good, off to the router with it */ + + log_debug(ZONE, "incoming packet on valid route, preparing it for the router"); + + /* rewrite server packets into client packets */ + ns = nad_find_namespace(nad, 0, uri_SERVER, NULL); + if(ns >= 0) { + if(nad->elems[0].ns == ns) + nad->elems[0].ns = nad->nss[nad->elems[0].ns].next; + else { + for(sns = nad->elems[0].ns; sns >= 0 && nad->nss[sns].next != ns; sns = nad->nss[sns].next); + nad->nss[sns].next = nad->nss[nad->nss[sns].next].next; + } + } + + ns = nad_find_namespace(nad, 0, uri_CLIENT, NULL); + if(ns < 0) { + ns = nad_add_namespace(nad, uri_CLIENT, NULL); + nad->scope = -1; + nad->nss[ns].next = nad->elems[0].ns; + nad->elems[0].ns = ns; + } + nad->elems[0].my_ns = ns; + + /* wrap up the packet */ + ns = nad_add_namespace(nad, uri_COMPONENT, "comp"); + + nad_wrap_elem(nad, 0, ns, "route"); + + nad_set_attr(nad, 0, -1, "to", to->domain, 0); + nad_set_attr(nad, 0, -1, "from", in->s2s->id, 0); /* route is from s2s, not packet source */ + + log_debug(ZONE, "sending packet to %s", to->domain); + + /* go */ + sx_nad_write(in->s2s->router, nad); + + jid_free(from); + jid_free(to); +} diff --git a/s2s/main.c b/s2s/main.c new file mode 100644 index 00000000..69936138 --- /dev/null +++ b/s2s/main.c @@ -0,0 +1,671 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "s2s.h" + +static sig_atomic_t s2s_shutdown = 0; +sig_atomic_t s2s_lost_router = 0; +static sig_atomic_t s2s_logrotate = 0; + +static void _s2s_signal(int signum) { + s2s_shutdown = 1; + s2s_lost_router = 0; +} + +static void _s2s_signal_hup(int signum) { + s2s_logrotate = 1; +} + +/** store the process id */ +static void _s2s_pidfile(s2s_t s2s) { + char *pidfile; + FILE *f; + pid_t pid; + + pidfile = config_get_one(s2s->config, "pidfile", 0); + if(pidfile == NULL) + return; + + pid = getpid(); + + if((f = fopen(pidfile, "w+")) == NULL) { + log_write(s2s->log, LOG_ERR, "couldn't open %s for writing: %s", pidfile, strerror(errno)); + return; + } + + if(fprintf(f, "%d", pid) < 0) { + log_write(s2s->log, LOG_ERR, "couldn't write to %s: %s", pidfile, strerror(errno)); + return; + } + + fclose(f); + + log_write(s2s->log, LOG_INFO, "process id is %d, written to %s", pid, pidfile); +} + +/** pull values out of the config file */ +static void _s2s_config_expand(s2s_t s2s) { + char *str, secret[41]; + int i, r; + + s2s->id = config_get_one(s2s->config, "id", 0); + if(s2s->id == NULL) + s2s->id = "s2s"; + + s2s->router_ip = config_get_one(s2s->config, "router.ip", 0); + if(s2s->router_ip == NULL) + s2s->router_ip = "127.0.0.1"; + + s2s->router_port = j_atoi(config_get_one(s2s->config, "router.port", 0), 5347); + + s2s->router_user = config_get_one(s2s->config, "router.user", 0); + if(s2s->router_user == NULL) + s2s->router_user = "jabberd"; + s2s->router_pass = config_get_one(s2s->config, "router.pass", 0); + if(s2s->router_pass == NULL) + s2s->router_pass = "secret"; + + s2s->router_pemfile = config_get_one(s2s->config, "router.pemfile", 0); + + s2s->retry_init = j_atoi(config_get_one(s2s->config, "router.retry.init", 0), 3); + s2s->retry_lost = j_atoi(config_get_one(s2s->config, "router.retry.lost", 0), 3); + if((s2s->retry_sleep = j_atoi(config_get_one(s2s->config, "router.retry.sleep", 0), 2)) < 1) + s2s->retry_sleep = 1; + + s2s->router_default = config_count(s2s->config, "router.non-default") ? 0 : 1; + + s2s->log_type = log_STDOUT; + if(config_get(s2s->config, "log") != NULL) { + if((str = config_get_attr(s2s->config, "log", 0, "type")) != NULL) { + if(strcmp(str, "file") == 0) + s2s->log_type = log_FILE; + else if(strcmp(str, "syslog") == 0) + s2s->log_type = log_SYSLOG; + } + } + + if(s2s->log_type == log_SYSLOG) { + s2s->log_facility = config_get_one(s2s->config, "log.facility", 0); + s2s->log_ident = config_get_one(s2s->config, "log.ident", 0); + if(s2s->log_ident == NULL) + s2s->log_ident = "jabberd/s2s"; + } else if(s2s->log_type == log_FILE) + s2s->log_ident = config_get_one(s2s->config, "log.file", 0); + + s2s->local_ip = config_get_one(s2s->config, "local.ip", 0); + if(s2s->local_ip == NULL) + s2s->local_ip = "0.0.0.0"; + + s2s->local_port = j_atoi(config_get_one(s2s->config, "local.port", 0), 0); + + s2s->local_resolver = config_get_one(s2s->config, "local.resolver", 0); + if(s2s->local_resolver == NULL) + s2s->local_resolver = "resolver"; + + if(config_get(s2s->config, "local.secret") != NULL) + s2s->local_secret = strdup(config_get_one(s2s->config, "local.secret", 0)); + else { + for(i = 0; i < 40; i++) { + r = (int) (36.0 * rand() / RAND_MAX); + secret[i] = (r >= 0 && r <= 9) ? (r + 48) : (r + 87); + } + secret[40] = '\0'; + + s2s->local_secret = strdup(secret); + } + + if(s2s->local_secret == NULL) + s2s->local_secret = "secret"; + + s2s->local_pemfile = config_get_one(s2s->config, "local.pemfile", 0); + if (s2s->local_pemfile != NULL) + log_debug(ZONE,"loaded local pemfile for peer s2s connections"); + + s2s->check_interval = j_atoi(config_get_one(s2s->config, "check.interval", 0), 60); + s2s->check_queue = j_atoi(config_get_one(s2s->config, "check.queue", 0), 60); + s2s->check_keepalive = j_atoi(config_get_one(s2s->config, "check.keepalive", 0), 0); + s2s->check_idle = j_atoi(config_get_one(s2s->config, "check.idle", 0), 86400); + +} + +static int _s2s_router_connect(s2s_t s2s) { + log_write(s2s->log, LOG_NOTICE, "attempting connection to router at %s, port=%d", s2s->router_ip, s2s->router_port); + + s2s->fd = mio_connect(s2s->mio, s2s->router_port, s2s->router_ip, s2s_router_mio_callback, (void *) s2s); + if(s2s->fd < 0) { + if(errno == ECONNREFUSED) + s2s_lost_router = 1; + log_write(s2s->log, LOG_NOTICE, "connection attempt to router failed: %s (%d)", strerror(errno), errno); + return 1; + } + + s2s->router = sx_new(s2s->sx_env, s2s->fd, s2s_router_sx_callback, (void *) s2s); + sx_client_init(s2s->router, 0, NULL, NULL, NULL, "1.0"); + + return 0; +} + +int _s2s_check_conn_routes(s2s_t s2s, conn_t conn, const char *direction) +{ + char *rkey; + conn_state_t state; + time_t now, dialback_time; + + now = time(NULL); + + if(xhash_iter_first(conn->states)) + do { + /* retrieve state in a separate operation, as sizeof(int) != sizeof(void *) on 64-bit platforms, + so passing a pointer to state in xhash_iter_get is unsafe */ + xhash_iter_get(conn->states, (const char **) &rkey, NULL); + state = (conn_state_t) xhash_get(conn->states, rkey); + + if (state == conn_INPROGRESS) { + dialback_time = (time_t) xhash_get(conn->states_time, rkey); + + if(now > dialback_time + s2s->check_queue) { + log_write(s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] dialback for %s route '%s' timed out", conn->fd, conn->ip, conn->port, direction, rkey); + + xhash_zap(conn->states, rkey); + xhash_zap(conn->states_time, rkey); + + /* stream error */ + sx_error(conn->s, stream_err_CONNECTION_TIMEOUT, "dialback timed out"); + + /* close connection as per XMPP/RFC3920 */ + sx_close(conn->s); + + /* indicate that we closed the connection */ + return 0; + } + } + } while(xhash_iter_next(conn->states)); + + /* all ok */ + return 1; +} + +static void _s2s_time_checks(s2s_t s2s) { + conn_t conn; + time_t now; + char *domain, ipport[INET6_ADDRSTRLEN + 17], *key; + jqueue_t q; + dnscache_t dns; + union xhashv xhv; + + now = time(NULL); + + /* queue expiry */ + if(s2s->check_queue > 0) { + if(xhash_iter_first(s2s->outq)) + do { + xhv.jq_val = &q; + xhash_iter_get(s2s->outq, (const char **) &domain, xhv.val); + + log_debug(ZONE, "running time checks for %s", domain); + + /* dns lookup timeout check first */ + dns = xhash_get(s2s->dnscache, domain); + if(dns == NULL) + continue; + + if(dns->pending) { + log_debug(ZONE, "dns lookup pending for %s", domain); + if(now > dns->init_time + s2s->check_queue) { + log_write(s2s->log, LOG_NOTICE, "dns lookup for %s timed out", domain); + + /* bounce queue */ + out_bounce_queue(s2s, domain, stanza_err_REMOTE_SERVER_NOT_FOUND); + + /* expire pending dns entry */ + xhash_zap(s2s->dnscache, dns->name); + free(dns); + } + + continue; + } + + /* generate the ip/port pair */ + snprintf(ipport, INET6_ADDRSTRLEN + 16, "%s/%d", dns->ip, dns->port); + + /* get the conn */ + conn = xhash_get(s2s->out, ipport); + if(conn == NULL) { + if(jqueue_size(q) > 0) { + /* no pending conn? perhaps it failed? */ + log_debug(ZONE, "no pending connection for %s, bouncing %i packets in queue", domain, jqueue_size(q)); + + /* bounce queue */ + out_bounce_queue(s2s, domain, stanza_err_REMOTE_SERVER_TIMEOUT); + } + + continue; + } + + /* connect timeout check */ + if(!conn->online && now > conn->init_time + s2s->check_queue) { + log_write(s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] connection to %s timed out", conn->fd, conn->ip, conn->port, domain); + + /* bounce queue */ + out_bounce_queue(s2s, domain, stanza_err_REMOTE_SERVER_TIMEOUT); + + /* close connection as per XMPP/RFC3920 */ + sx_close(conn->s); + + } + } while(xhash_iter_next(s2s->outq)); + } + + /* expiry of connected routes in conn_INPROGRESS state */ + if(s2s->check_queue > 0) { + + /* outgoing connections */ + if(xhash_iter_first(s2s->out)) + do { + xhv.conn_val = &conn; + xhash_iter_get(s2s->out, (const char **) &key, xhv.val); + log_debug(ZONE, "checking dialback state for outgoing conn %s", key); + if (_s2s_check_conn_routes(s2s, conn, "outgoing")) { + log_debug(ZONE, "checking pending verify requests for outgoing conn %s", key); + if (conn->verify > 0 && now > conn->last_verify + s2s->check_queue) { + log_write(s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] dialback verify request timed out", conn->fd, conn->ip, conn->port); + sx_error(conn->s, stream_err_CONNECTION_TIMEOUT, "dialback verify request timed out"); + sx_close(conn->s); + } + } + } while(xhash_iter_next(s2s->out)); + + /* incoming open streams */ + if(xhash_iter_first(s2s->in)) + do { + xhv.conn_val = &conn; + xhash_iter_get(s2s->in, (const char **) &key, xhv.val); + + log_debug(ZONE, "checking dialback state for incoming conn %s", key); + if (_s2s_check_conn_routes(s2s, conn, "incoming")) + /* if the connection is still valid, check that dialbacks have been initiated */ + if(!xhash_count(conn->states) && now > conn->init_time + s2s->check_queue) { + log_write(s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] no dialback started", conn->fd, conn->ip, conn->port); + sx_error(conn->s, stream_err_CONNECTION_TIMEOUT, "no dialback initiated"); + sx_close(conn->s); + } + } while(xhash_iter_next(s2s->in)); + + /* incoming open connections (not yet streams) */ + if(xhash_iter_first(s2s->in_accept)) + do { + xhv.conn_val = &conn; + xhash_iter_get(s2s->in_accept, (const char **) &key, xhv.val); + + log_debug(ZONE, "checking stream connection state for incoming conn %i", conn->fd); + if(!conn->online && now > conn->init_time + s2s->check_queue) { + log_write(s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] stream initiation timed out", conn->fd, conn->ip, conn->port); + sx_close(conn->s); + } + } while(xhash_iter_next(s2s->in_accept)); + + } + + /* keepalives */ + if(xhash_iter_first(s2s->out)) + do { + xhv.conn_val = &conn; + xhash_iter_get(s2s->out, NULL, xhv.val); + + if(s2s->check_keepalive > 0 && conn->last_activity > 0 && now > conn->last_activity + s2s->check_keepalive && conn->s->state >= state_STREAM) { + log_debug(ZONE, "sending keepalive for %d", conn->fd); + + sx_raw_write(conn->s, " ", 1); + } + } while(xhash_iter_next(s2s->out)); + + /* idle timeouts - disconnect connections through which no packets have been sent for seconds */ + if(s2s->check_idle > 0) { + + /* outgoing connections */ + if(xhash_iter_first(s2s->out)) + do { + xhv.conn_val = &conn; + xhash_iter_get(s2s->out, (const char **) &key, xhv.val); + log_debug(ZONE, "checking idle state for %s", key); + if (conn->last_packet > 0 && now > conn->last_packet + s2s->check_idle && conn->s->state >= state_STREAM) { + log_write(s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] idle timeout", conn->fd, conn->ip, conn->port); + sx_close(conn->s); + } + } while(xhash_iter_next(s2s->out)); + + /* incoming connections */ + if(xhash_iter_first(s2s->in)) + do { + xhv.conn_val = &conn; + xhash_iter_get(s2s->in, (const char **) &key, xhv.val); + log_debug(ZONE, "checking idle state for %s", key); + if (conn->last_packet > 0 && now > conn->last_packet + s2s->check_idle && conn->s->state >= state_STREAM) { + log_write(s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] idle timeout", conn->fd, conn->ip, conn->port); + sx_close(conn->s); + } + } while(xhash_iter_next(s2s->in)); + + } + + return; +} + +int main(int argc, char **argv) { + s2s_t s2s; + char *config_file; + int optchar; + conn_t conn; + jqueue_t q; + dnscache_t dns; + union xhashv xhv; +#ifdef POOL_DEBUG + time_t pool_time = 0; +#endif + +#ifdef HAVE_UMASK + umask((mode_t) 0027); +#endif + + srand(time(NULL)); + +#ifdef HAVE_WINSOCK2_H +/* get winsock running */ + { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + /* !!! tell user that we couldn't find a usable winsock dll */ + return 0; + } + } +#endif + + jabber_signal(SIGINT, _s2s_signal); + jabber_signal(SIGTERM, _s2s_signal); +#ifdef SIGHUP + jabber_signal(SIGHUP, _s2s_signal_hup); +#endif +#ifdef SIGPIPE + jabber_signal(SIGPIPE, SIG_IGN); +#endif + + s2s = (s2s_t) malloc(sizeof(struct s2s_st)); + memset(s2s, 0, sizeof(struct s2s_st)); + + /* load our config */ + s2s->config = config_new(); + + config_file = CONFIG_DIR "/s2s.xml"; + + /* cmdline parsing */ + while((optchar = getopt(argc, argv, "Dc:h?")) >= 0) + { + switch(optchar) + { + case 'c': + config_file = optarg; + break; + case 'D': +#ifdef DEBUG + set_debug_flag(1); +#else + printf("WARN: Debugging not enabled. Ignoring -D.\n"); +#endif + break; + case 'h': case '?': default: + fputs( + "s2s - jabberd server-to-server connector (" VERSION ")\n" + "Usage: s2s \n" + "Options are:\n" + " -c config file to use [default: " CONFIG_DIR "/s2s.xml]\n" +#ifdef DEBUG + " -D Show debug output\n" +#endif + , + stdout); + config_free(s2s->config); + free(s2s); + return 1; + } + } + + if(config_load(s2s->config, config_file) != 0) { + fputs("s2s: couldn't load config, aborting\n", stderr); + config_free(s2s->config); + free(s2s); + return 2; + } + + _s2s_config_expand(s2s); + + s2s->log = log_new(s2s->log_type, s2s->log_ident, s2s->log_facility); + log_write(s2s->log, LOG_NOTICE, "starting up (interval=%i, queue=%i, keepalive=%i, idle=%i)", s2s->check_interval, s2s->check_queue, s2s->check_keepalive, s2s->check_idle); + + _s2s_pidfile(s2s); + + s2s->outq = xhash_new(401); + s2s->out = xhash_new(401); + s2s->in = xhash_new(401); + s2s->in_accept = xhash_new(401); + s2s->dnscache = xhash_new(401); + + s2s->pc = prep_cache_new(); + + s2s->dead = jqueue_new(); + s2s->dead_conn = jqueue_new(); + + s2s->sx_env = sx_env_new(); + +#ifdef HAVE_SSL + /* get the ssl context up and running */ + if(s2s->local_pemfile != NULL) { + s2s->sx_ssl = sx_env_plugin(s2s->sx_env, sx_ssl_init, s2s->local_pemfile, + s2s->local_cachain, s2s->local_verify_mode); + + if(s2s->sx_ssl == NULL) { + log_write(s2s->log, LOG_ERR, "failed to load local SSL pemfile, SSL will not be available to peers"); + s2s->local_pemfile = NULL; + } else + log_debug(ZONE, "loaded pemfile for SSL connections to peers"); + } + + /* try and get something online, so at least we can encrypt to the router */ + if(s2s->sx_ssl == NULL && s2s->router_pemfile != NULL) { + s2s->sx_ssl = sx_env_plugin(s2s->sx_env, sx_ssl_init, s2s->router_pemfile, NULL); + if(s2s->sx_ssl == NULL) { + log_write(s2s->log, LOG_ERR, "failed to load router SSL pemfile, channel to router will not be SSL encrypted"); + s2s->router_pemfile = NULL; + } + } +#endif + + /* get sasl online */ + s2s->sx_sasl = sx_env_plugin(s2s->sx_env, sx_sasl_init, "xmpp", SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT, NULL, NULL, 0); + if(s2s->sx_sasl == NULL) { + log_write(s2s->log, LOG_ERR, "failed to initialise SASL context, aborting"); + exit(1); + } + + s2s->sx_db = sx_env_plugin(s2s->sx_env, s2s_db_init); + + s2s->mio = mio_new(1024); + + s2s->retry_left = s2s->retry_init; + _s2s_router_connect(s2s); + + while(!s2s_shutdown) { + mio_run(s2s->mio, 5); + + if(s2s_logrotate) { + log_write(s2s->log, LOG_NOTICE, "reopening log ..."); + log_free(s2s->log); + s2s->log = log_new(s2s->log_type, s2s->log_ident, s2s->log_facility); + log_write(s2s->log, LOG_NOTICE, "log started"); + + s2s_logrotate = 0; + } + + if(s2s_lost_router) { + if(s2s->retry_left < 0) { + log_write(s2s->log, LOG_NOTICE, "attempting reconnect"); + sleep(s2s->retry_sleep); + s2s_lost_router = 0; + _s2s_router_connect(s2s); + } + + else if(s2s->retry_left == 0) { + s2s_shutdown = 1; + } + + else { + log_write(s2s->log, LOG_NOTICE, "attempting reconnect (%d left)", s2s->retry_left); + s2s->retry_left--; + sleep(s2s->retry_sleep); + s2s_lost_router = 0; + _s2s_router_connect(s2s); + } + } + + /* cleanup dead sx_ts */ + while(jqueue_size(s2s->dead) > 0) + sx_free((sx_t) jqueue_pull(s2s->dead)); + + /* cleanup dead conn_ts */ + while(jqueue_size(s2s->dead_conn) > 0) { + conn = (conn_t) jqueue_pull(s2s->dead_conn); + xhash_free(conn->states); + xhash_free(conn->states_time); + xhash_free(conn->routes); + + if(conn->key != NULL) free(conn->key); + free(conn); + } + + /* time checks */ + if(s2s->check_interval > 0 && time(NULL) >= s2s->next_check) { + log_debug(ZONE, "running time checks"); + + _s2s_time_checks(s2s); + + s2s->next_check = time(NULL) + s2s->check_interval; + log_debug(ZONE, "next time check at %d", s2s->next_check); + } + +#ifdef POOL_DEBUG + if(time(NULL) > pool_time + 60) { + pool_stat(1); + pool_time = time(NULL); + } +#endif + } + + log_write(s2s->log, LOG_NOTICE, "shutting down"); + + /* close active streams gracefully */ + xhv.conn_val = &conn; + if(xhash_iter_first(s2s->out)) + do { + xhash_iter_get(s2s->out, NULL, xhv.val); + sx_error(conn->s, stream_err_SYSTEM_SHUTDOWN, "s2s shutdown"); + sx_close(conn->s); + } while(xhash_count(s2s->out)); + + if(xhash_iter_first(s2s->in)) + do { + xhash_iter_get(s2s->in, NULL, xhv.val); + sx_error(conn->s, stream_err_SYSTEM_SHUTDOWN, "s2s shutdown"); + sx_close(conn->s); + } while(xhash_count(s2s->in)); + + if(xhash_iter_first(s2s->in_accept)) + do { + xhash_iter_get(s2s->in_accept, NULL, xhv.val); + sx_close(conn->s); + } while(xhash_count(s2s->in_accept)); + + + /* remove dead streams */ + while(jqueue_size(s2s->dead) > 0) + sx_free((sx_t) jqueue_pull(s2s->dead)); + + /* cleanup dead conn_ts */ + while(jqueue_size(s2s->dead_conn) > 0) { + conn = (conn_t) jqueue_pull(s2s->dead_conn); + xhash_free(conn->states); + xhash_free(conn->states_time); + xhash_free(conn->routes); + + if(conn->key != NULL) free(conn->key); + free(conn); + } + + /* free outgoing queues */ + xhv.jq_val = &q; + if(xhash_iter_first(s2s->outq)) + do { + xhash_iter_get(s2s->outq, NULL, xhv.val); + jqueue_free(q); + } while(xhash_iter_next(s2s->outq)); + + /* walk & free resolve queues */ + xhv.dns_val = &dns; + if(xhash_iter_first(s2s->dnscache)) + do { + xhash_iter_get(s2s->dnscache, NULL, xhv.val); + free(dns); + } while(xhash_iter_next(s2s->dnscache)); + + /* free hashes */ + xhash_free(s2s->outq); + xhash_free(s2s->out); + xhash_free(s2s->in); + xhash_free(s2s->in_accept); + xhash_free(s2s->dnscache); + + prep_cache_free(s2s->pc); + + jqueue_free(s2s->dead); + jqueue_free(s2s->dead_conn); + + sx_free(s2s->router); + + sx_env_free(s2s->sx_env); + + mio_free(s2s->mio); + + log_free(s2s->log); + + config_free(s2s->config); + + free(s2s->local_secret); + free(s2s); + +#ifdef POOL_DEBUG + pool_stat(1); +#endif + + return 0; +} diff --git a/s2s/out.c b/s2s/out.c new file mode 100644 index 00000000..f7bae8ee --- /dev/null +++ b/s2s/out.c @@ -0,0 +1,922 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "s2s.h" + +/* + * we handle packets going from the router to the world, and stuff + * that comes in on connections we initiated. + * + * action points: + * + * out_packet(s2s, nad) - send this packet out + * - extract to domain + * - check internal resolver cache for ip/port + * - if not found + * - add packet to queue for this domain + * - ask resolver for name + * - DONE + * - get dbconn for this ip/port + * - if dbconn not found + * - add packet to queue for this domain + * - create new dbconn (key ip/port) + * - initiate connect to ip/port + * - DONE + * - if conn in progress (tcp) + * - add packet to queue for this domain + * - DONE + * - if dbconn state valid for this domain, or packet is dialback + * - send packet + * - DONE + * - if dbconn state invalid for this domain + * - bounce packet (502) + * - DONE + * - add packet to queue for this domain + * - if dbconn state inprogress for this domain + * - DONE + * - out_dialback(dbconn, from, to) + * + * out_dialback(dbconn, from, to) - initiate dialback + * - generate dbkey: sha1(secret+remote+stream id) + * - send auth request: dbkey + * - set dbconn state for this domain to inprogress + * - DONE + * + * out_resolve(s2s, nad) - responses from resolver + * - store ip/port/ttl in resolver cache + * - flush domain queue -> out_packet(s2s, nad) + * - for each packet in queue for this domain + * - DONE + * + * event_STREAM - ip/port open + * - get dbconn for this sx + * - for each route handled by this conn, out_dialback(dbconn, from, to) + * - DONE + * + * event_PACKET: - response to our auth request + * - get dbconn for this sx + * - if type valid + * - set dbconn state for this domain to valid + * - flush dbconn queue for this domain -> out_packet(s2s, pkt) + * - DONE + * - set dbconn state for this domain to invalid + * - bounce dbconn queue for this domain (502) + * - DONE + * + * event_PACKET: - incoming stream authenticated + * - get dbconn for given id + * - if type is valid + * - set dbconn state for this domain to valid + * - send result: + * - DONE + */ + +/* forward decls */ +static int _out_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg); +static int _out_sx_callback(sx_t s, sx_event_t e, void *data, void *arg); +static void _out_result(conn_t out, nad_t nad); +static void _out_verify(conn_t out, nad_t nad); + +/** queue the packet */ +static void _out_packet_queue(s2s_t s2s, pkt_t pkt) { + jqueue_t q = (jqueue_t) xhash_get(s2s->outq, pkt->to->domain); + + if(q == NULL) { + log_debug(ZONE, "creating new out packet queue for %s", pkt->to->domain); + q = jqueue_new(); + xhash_put(s2s->outq, pstrdup(xhash_pool(s2s->outq), pkt->to->domain), (void *) q); + } + + log_debug(ZONE, "queueing packet for %s", pkt->to->domain); + + jqueue_push(q, (void *) pkt, 0); +} + +static void _out_dialback(conn_t out, char *rkey) { + char *c, *dbkey; + nad_t nad; + int ns; + time_t now; + + now = time(NULL); + + c = strchr(rkey, '/'); + *c = '\0'; + c++; + + /* kick off the dialback */ + dbkey = s2s_db_key(NULL, out->s2s->local_secret, c, out->s->id); + + nad = nad_new(out->s->nad_cache); + + /* request auth */ + ns = nad_add_namespace(nad, uri_DIALBACK, "db"); + nad_append_elem(nad, ns, "result", 0); + nad_append_attr(nad, -1, "from", rkey); + nad_append_attr(nad, -1, "to", c); + nad_append_cdata(nad, dbkey, strlen(dbkey), 1); + + c--; + *c = '/'; + + log_debug(ZONE, "sending auth request for %s (key %s)", rkey, dbkey); + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] sending dialback auth request for route '%s'", out->fd, out->ip, out->port, rkey); + + /* off it goes */ + sx_nad_write(out->s, nad); + + free(dbkey); + + /* we're in progress now */ + xhash_put(out->states, pstrdup(xhash_pool(out->states), rkey), (void *) conn_INPROGRESS); + + /* record the time that we set conn_INPROGRESS state */ + xhash_put(out->states_time, pstrdup(xhash_pool(out->states_time), rkey), (void *) now); +} + +/** send a packet out */ +void out_packet(s2s_t s2s, pkt_t pkt) { + int ns; + dnscache_t dns; + nad_t nad; + char ipport[INET6_ADDRSTRLEN + 16], *rkey; + conn_t out; + conn_state_t state; + + /* check resolver cache for ip/port */ + dns = xhash_get(s2s->dnscache, pkt->to->domain); + if(dns == NULL) { + /* new resolution */ + log_debug(ZONE, "no dns for %s, preparing for resolution", pkt->to->domain); + + dns = (dnscache_t) malloc(sizeof(struct dnscache_st)); + memset(dns, 0, sizeof(struct dnscache_st)); + + strcpy(dns->name, pkt->to->domain); + + xhash_put(s2s->dnscache, dns->name, (void *) dns); + +#if 0 + /* this is good for testing */ + dns->pending = 0; + strcpy(dns->ip, "127.0.0.1"); + dns->port = 3000; + dns->expiry = time(NULL) + 99999999; +#endif + } + + /* resolution in progress */ + if(dns->pending) { + log_debug(ZONE, "pending resolution, queueing packet"); + + _out_packet_queue(s2s, pkt); + + return; + } + + /* has it expired (this is 0 for new cache objects, so they're always expired */ + if(time(NULL) > dns->expiry) { + /* it has, queue the packet */ + _out_packet_queue(s2s, pkt); + + /* resolution required */ + log_debug(ZONE, "requesting resolution for %s", pkt->to->domain); + + nad = nad_new(s2s->router->nad_cache); + + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, ns, "route", 0); + nad_append_attr(nad, -1, "from", s2s->id); + nad_append_attr(nad, -1, "to", s2s->local_resolver); + + ns = nad_add_namespace(nad, uri_RESOLVER, NULL); + + nad_append_elem(nad, ns, "resolve", 1); + nad_append_attr(nad, -1, "type", "query"); + nad_append_attr(nad, -1, "name", pkt->to->domain); + + sx_nad_write(s2s->router, nad); + + dns->init_time = time(NULL); + + dns->pending = 1; + + return; + } + + /* dns is valid */ + strcpy(pkt->ip, dns->ip); + pkt->port = dns->port; + + /* generate the ip/port pair, this is the hash key for the conn */ + snprintf(ipport, INET6_ADDRSTRLEN + 16, "%s/%d", pkt->ip, pkt->port); + out = (conn_t) xhash_get(s2s->out, ipport); + + /* new route key */ + rkey = s2s_route_key(NULL, pkt->from->domain, pkt->to->domain); + + /* if no connection, queue the packet and set up connection */ + if(out == NULL) { + _out_packet_queue(s2s, pkt); + + /* no conn, create one */ + out = (conn_t) malloc(sizeof(struct conn_st)); + memset(out, 0, sizeof(struct conn_st)); + + out->s2s = s2s; + + out->key = strdup(ipport); + + strcpy(out->ip, pkt->ip); + out->port = pkt->port; + + out->states = xhash_new(101); + out->states_time = xhash_new(101); + + out->routes = xhash_new(101); + + out->init_time = time(NULL); + + xhash_put(s2s->out, out->key, (void *) out); + + xhash_put(out->routes, pstrdup(xhash_pool(out->routes), rkey), (void *) 1); + + /* connect */ + log_debug(ZONE, "initiating connection to %s", ipport); + + out->fd = mio_connect(s2s->mio, pkt->port, pkt->ip, _out_mio_callback, (void *) out); + + if (out->fd < 0) { + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] mio_connect error: %s (%d)", out->fd, out->ip, out->port, strerror(errno), errno); + + /* bounce queues */ + out_bounce_queue(s2s, pkt->to->domain, stanza_err_SERVICE_UNAVAILABLE); + + xhash_zap(s2s->out, ipport); + + xhash_free(out->states); + xhash_free(out->states_time); + + xhash_free(out->routes); + + free(out->key); + free(out); + } else { + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] outgoing connection", out->fd, out->ip, out->port); + + out->s = sx_new(s2s->sx_env, out->fd, _out_sx_callback, (void *) out); + +#ifdef HAVE_SSL + /* Send a stream version of 1.0 if we can do STARTTLS */ + if(out->s2s->sx_ssl != NULL && out->s2s->local_pemfile != NULL) { + sx_client_init(out->s, S2S_DB_HEADER, uri_SERVER, pkt->to->domain, NULL, "1.0"); + } else { + sx_client_init(out->s, S2S_DB_HEADER, uri_SERVER, NULL, NULL, NULL); + } +#else + sx_client_init(out->s, S2S_DB_HEADER, uri_SERVER, NULL, NULL, NULL); +#endif + } + + free(rkey); + + return; + } + + /* connection in progress */ + if(!out->online) { + log_debug(ZONE, "connection in progress, queueing packet"); + + _out_packet_queue(s2s, pkt); + + xhash_put(out->routes, pstrdup(xhash_pool(out->routes), rkey), (void *) 1); + + free(rkey); + + return; + } + + /* connection state */ + state = (conn_state_t) xhash_get(out->states, rkey); + + /* valid conns or dialback packets */ + if(state == conn_VALID || pkt->db) { + log_debug(ZONE, "writing packet for %s to outgoing conn %s", rkey, ipport); + + /* send it straight out */ + if(pkt->db) { + /* dialback packet */ + sx_nad_write(out->s, pkt->nad); + + /* if this is a db:verify packet, increment counter and set timestamp */ + if(NAD_ENAME_L(pkt->nad, 0) == 6 && strncmp("verify", NAD_ENAME(pkt->nad, 0), 6) == 0) { + out->verify++; + out->last_verify = time(NULL); + } + } else { + /* if the outgoing stanza has a jabber:client namespace, remove it so that the stream jabber:server namespaces will apply (XMPP 11.2.2) */ + int ns = nad_find_namespace(pkt->nad, 1, uri_CLIENT, NULL); + if(ns >= 0) { + /* clear the namespaces of elem 0 (internal route element) and elem 1 (message|iq|presence) */ + pkt->nad->elems[0].ns = -1; + pkt->nad->elems[0].my_ns = -1; + pkt->nad->elems[1].ns = -1; + pkt->nad->elems[1].my_ns = -1; + } + + /* send it out */ + sx_nad_write_elem(out->s, pkt->nad, 1); + } + + /* update timestamp */ + out->last_packet = time(NULL); + + jid_free(pkt->from); + jid_free(pkt->to); + free(pkt); + + free(rkey); + + return; + } + + /* can't be handled yet, queue */ + _out_packet_queue(s2s, pkt); + + /* if dialback is in progress, then we're done for now */ + if(state == conn_INPROGRESS) { + free(rkey); + return; + } + + /* this is a new route - send dialback auth request to piggyback on the existing connection */ + _out_dialback(out, rkey); + + free(rkey); +} + +/** responses from the resolver */ +void out_resolve(s2s_t s2s, nad_t nad) { + int attr, port = 0, ttl = 0, npkt, i; + jid_t name; + char ip[INET6_ADDRSTRLEN], str[16]; + dnscache_t dns; + jqueue_t q; + pkt_t pkt; + + attr = nad_find_attr(nad, 1, -1, "name", NULL); + name = jid_new(s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + /* no results, resolve failed */ + if(nad->ecur == 2) { + dns = xhash_get(s2s->dnscache, name->domain); + xhash_zap(s2s->dnscache, name->domain); + free(dns); + + log_write(s2s->log, LOG_NOTICE, "dns lookup for %s failed", name->domain); + + /* bounce queue */ + out_bounce_queue(s2s, name->domain, stanza_err_REMOTE_SERVER_NOT_FOUND); + + /* delete queue for domain and remove domain from queue hash */ + q = (jqueue_t) xhash_get(s2s->outq, name->domain); + if(q != NULL) + jqueue_free(q); + xhash_zap(s2s->outq, name->domain); + + jid_free(name); + nad_free(nad); + + return; + } + + snprintf(ip, INET6_ADDRSTRLEN, "%.*s", NAD_CDATA_L(nad, 2), NAD_CDATA(nad, 2)); + + attr = nad_find_attr(nad, 2, -1, "port", NULL); + if(attr >= 0) { + snprintf(str, 16, "%.*s", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + port = atoi(str); + } + if(port == 0) + port = 5269; + + attr = nad_find_attr(nad, 2, -1, "ttl", NULL); + if(attr >= 0) { + snprintf(str, 16, "%.*s", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + ttl = atoi(str); + } + + log_debug(ZONE, "%s resolved to %s, port %d, ttl %d", name->domain, ip, port, ttl); + + /* get the cache entry */ + dns = xhash_get(s2s->dnscache, name->domain); + if(dns == NULL) { + log_debug(ZONE, "weird, we never requested this"); + jid_free(name); + nad_free(nad); + return; + } + + /* fill it out */ + strcpy(dns->ip, ip); + dns->port = port; + dns->expiry = time(NULL) + ttl; + dns->pending = 0; + + q = (jqueue_t) xhash_get(s2s->outq, name->domain); + npkt = jqueue_size(q); + + if(q == NULL || npkt == 0) { + /* weird */ + log_debug(ZONE, "nonexistent or empty queue for domain, we're done"); + jid_free(name); + nad_free(nad); + return; + } + + log_debug(ZONE, "flushing %d packets to out_packet", npkt); + + for(i = 0; i < npkt; i++) { + pkt = jqueue_pull(q); + out_packet(s2s, pkt); + } + + jid_free(name); + nad_free(nad); +} + +/** mio callback for outgoing conns */ +static int _out_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) { + conn_t out = (conn_t) arg; + char ipport[INET6_ADDRSTRLEN + 17]; + int nbytes; + + switch(a) { + case action_READ: + log_debug(ZONE, "read action on fd %d", fd); + + /* they did something */ + out->last_activity = time(NULL); + + ioctl(fd, FIONREAD, &nbytes); + if(nbytes == 0) { + sx_kill(out->s); + return 0; + } + + return sx_can_read(out->s); + + case action_WRITE: + log_debug(ZONE, "write action on fd %d", fd); + + /* update activity timestamp */ + out->last_activity = time(NULL); + + return sx_can_write(out->s); + + case action_CLOSE: + log_debug(ZONE, "close action on fd %d", fd); + + /* bounce queues */ + out_bounce_conn_queues(out, stanza_err_SERVICE_UNAVAILABLE); + + jqueue_push(out->s2s->dead, (void *) out->s, 0); + + /* generate the ip/port pair */ + snprintf(ipport, INET6_ADDRSTRLEN + 16, "%s/%d", out->ip, out->port); + + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] disconnect", fd, out->ip, out->port); + + xhash_zap(out->s2s->out, ipport); + + jqueue_push(out->s2s->dead_conn, (void *) out, 0); + + case action_ACCEPT: + break; + } + + return 0; +} + +void send_dialbacks(conn_t out) +{ + char *rkey; + + if (xhash_iter_first(out->routes)) { + log_debug(ZONE, "sending dialback packets for %s", out->key); + do { + xhash_iter_get(out->routes, (const char **) &rkey, NULL); + _out_dialback(out, rkey); + } while(xhash_iter_next(out->routes)); + } + + return; +} + +static int _out_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + conn_t out = (conn_t) arg; + sx_buf_t buf = (sx_buf_t) data; + int len, ns, elem, starttls = 0; + sx_error_t *sxe; + nad_t nad; + + switch(e) { + case event_WANT_READ: + log_debug(ZONE, "want read"); + mio_read(out->s2s->mio, out->fd); + break; + + case event_WANT_WRITE: + log_debug(ZONE, "want write"); + mio_write(out->s2s->mio, out->fd); + break; + + case event_READ: + log_debug(ZONE, "reading from %d", out->fd); + + /* do the read */ + len = recv(out->fd, buf->data, buf->len, 0); + + if(len < 0) { + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) { + buf->len = 0; + return 0; + } + + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] read error: %s (%d)", out->fd, out->ip, out->port, strerror(errno), errno); + + sx_kill(s); + + return -1; + } + + else if(len == 0) { + /* they went away */ + sx_kill(s); + + return -1; + } + + log_debug(ZONE, "read %d bytes", len); + + buf->len = len; + + return len; + + case event_WRITE: + log_debug(ZONE, "writing to %d", out->fd); + + len = send(out->fd, buf->data, buf->len, 0); + if(len >= 0) { + log_debug(ZONE, "%d bytes written", len); + return len; + } + + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) + return 0; + + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] write error: %s (%d)", out->fd, out->ip, out->port, strerror(errno), errno); + + sx_kill(s); + + return -1; + + case event_ERROR: + sxe = (sx_error_t *) data; + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] error: %s (%s)", out->fd, out->ip, out->port, sxe->generic, sxe->specific); + + break; + + case event_OPEN: + log_debug(ZONE, "OPEN event for %s", out->key); + break; + + case event_STREAM: + /* check stream version - NULl = pre-xmpp (some jabber1 servers) */ + log_debug(ZONE, "STREAM event for %s stream version is %s", out->key, out->s->res_version); + + /* first time, bring them online */ + if(!out->online) { + log_debug(ZONE, "outgoing conn to %s is online", out->key); + + /* if no stream version from either side, kick off dialback for each route, */ + /* otherwise wait for stream features */ + if ((out->s->res_version==NULL) || (out->s2s->sx_ssl == NULL) || (out->s2s->local_pemfile == NULL)) { + log_debug(ZONE, "no stream version, sending dialbacks for %s immediately", out->key); + out->online = 1; + send_dialbacks(out); + } else + log_debug(ZONE, "outgoing conn to %s - waiting for STREAM features", out->key); + } + + break; + + case event_PACKET: + nad = (nad_t) data; + + /* watch for the features packet - STARTTLS and/or SASL*/ + if ((out->s->res_version!=NULL) + && NAD_NURI_L(nad, NAD_ENS(nad, 0)) == strlen(uri_STREAMS) + && strncmp(uri_STREAMS, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_STREAMS)) == 0 + && NAD_ENAME_L(nad, 0) == 8 && strncmp("features", NAD_ENAME(nad, 0), 8) == 0) { + log_debug(ZONE, "got the stream features packet"); + +#ifdef HAVE_SSL + /* starttls if we can */ + if(out->s2s->sx_ssl != NULL && out->s2s->local_pemfile != NULL && s->ssf == 0) { + ns = nad_find_scoped_namespace(nad, uri_TLS, NULL); + if(ns >= 0) { + elem = nad_find_elem(nad, 0, ns, "starttls", 1); + if(elem >= 0) { + log_debug(ZONE, "got STARTTLS in stream features"); + if(sx_ssl_client_starttls(out->s2s->sx_ssl, s, out->s2s->local_pemfile) == 0) { + starttls = 1; + nad_free(nad); + return 0; + } + log_write(out->s2s->log, LOG_ERR, "unable to establish encrypted session with peer"); + } + } + } + + /* If we're not establishing a starttls connection, send dialbacks */ + if (!starttls) { + log_debug(ZONE, "No STARTTLS, sending dialbacks for %s", out->key); + out->online = 1; + send_dialbacks(out); + } +#else + out->online = 1; + send_dialbacks(out); +#endif + } + + + /* we only accept dialback packets */ + if(NAD_ENS(nad, 0) < 0 || NAD_NURI_L(nad, NAD_ENS(nad, 0)) != uri_DIALBACK_L || strncmp(uri_DIALBACK, NAD_NURI(nad, NAD_ENS(nad, 0)), uri_DIALBACK_L) != 0) { + log_debug(ZONE, "got a non-dialback packet on an outgoing conn, dropping it"); + nad_free(nad); + return 0; + } + + /* and then only result and verify */ + if(NAD_ENAME_L(nad, 0) == 6) { + if(strncmp("result", NAD_ENAME(nad, 0), 6) == 0) { + _out_result(out, nad); + return 0; + } + + if(strncmp("verify", NAD_ENAME(nad, 0), 6) == 0) { + _out_verify(out, nad); + return 0; + } + } + + log_debug(ZONE, "unknown dialback packet, dropping it"); + + nad_free(nad); + return 0; + + case event_CLOSED: + mio_close(out->s2s->mio, out->fd); + + break; + } + + return 0; +} + +/** process incoming auth responses */ +static void _out_result(conn_t out, nad_t nad) { + int attr; + jid_t from, to; + char *rkey, *c; + jqueue_t q; + int npkt, i; + pkt_t pkt; + + attr = nad_find_attr(nad, 0, -1, "from", NULL); + if(attr < 0 || (from = jid_new(out->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid from on db result packet"); + nad_free(nad); + return; + } + + attr = nad_find_attr(nad, 0, -1, "to", NULL); + if(attr < 0 || (to = jid_new(out->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid to on db result packet"); + jid_free(from); + nad_free(nad); + return; + } + + rkey = s2s_route_key(NULL, to->domain, from->domain); + + /* key is valid */ + if(nad_find_attr(nad, 0, -1, "type", "valid") >= 0) { + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] outgoing route '%s' is now valid%s", out->fd, out->ip, out->port, rkey, out->s->ssf ? ", SSL negotiated" : ""); + + xhash_put(out->states, pstrdup(xhash_pool(out->states), rkey), (void *) conn_VALID); /* !!! small leak here */ + + log_debug(ZONE, "%s valid, flushing queue", rkey); + + /* to domain */ + c = strchr(rkey, '/'); + c++; + + /* flush the queue */ + q = (jqueue_t) xhash_get(out->s2s->outq, c); + if(q == NULL || (npkt = jqueue_size(q)) == 0) { + /* weird */ + log_debug(ZONE, "nonexistent or empty queue for domain, we're done"); + free(rkey); + jid_free(from); + jid_free(to); + nad_free(nad); + return; + } + + log_debug(ZONE, "flushing %d packets to out_packet", npkt); + + for(i = 0; i < npkt; i++) { + pkt = jqueue_pull(q); + out_packet(out->s2s, pkt); + } + + free(rkey); + + jid_free(from); + jid_free(to); + + nad_free(nad); + + return; + } + + /* invalid */ + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] outgoing route '%s' is now invalid", out->fd, out->ip, out->port, rkey); + + /* close connection */ + log_write(out->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] closing connection", out->fd, out->ip, out->port); + + /* report stream error */ + sx_error(out->s, stream_err_INVALID_ID, "dialback negotiation failed"); + + /* close the stream */ + sx_close(out->s); + + free(rkey); + + jid_free(from); + jid_free(to); + + nad_free(nad); +} + +/** incoming stream authenticated */ +static void _out_verify(conn_t out, nad_t nad) { + int attr, ns; + jid_t from, to; + conn_t in; + char *rkey; + int valid; + + attr = nad_find_attr(nad, 0, -1, "from", NULL); + if(attr < 0 || (from = jid_new(out->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid from on db verify packet"); + nad_free(nad); + return; + } + + attr = nad_find_attr(nad, 0, -1, "to", NULL); + if(attr < 0 || (to = jid_new(out->s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr))) == NULL) { + log_debug(ZONE, "missing or invalid to on db verify packet"); + jid_free(from); + nad_free(nad); + return; + } + + attr = nad_find_attr(nad, 0, -1, "id", NULL); + if(attr < 0) { + log_debug(ZONE, "missing id on db verify packet"); + jid_free(from); + jid_free(to); + nad_free(nad); + return; + } + + /* get the incoming conn */ + in = xhash_getx(out->s2s->in, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + if(in == NULL) { + log_debug(ZONE, "got a verify for incoming conn %.*s, but it doesn't exist, dropping the packet", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + jid_free(from); + jid_free(to); + nad_free(nad); + return; + } + + rkey = s2s_route_key(NULL, to->domain, from->domain); + + attr = nad_find_attr(nad, 0, -1, "type", "valid"); + if(attr >= 0) { + xhash_put(in->states, pstrdup(xhash_pool(in->states), rkey), (void *) conn_VALID); + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] incoming route '%s' is now valid%s", in->fd, in->ip, in->port, rkey, in->s->ssf ? ", SSL negotiated" : ""); + valid = 1; + } else { + log_write(in->s2s->log, LOG_NOTICE, "[%d] [%s, port=%d] incoming route '%s' is now invalid", in->fd, in->ip, in->port, rkey); + valid = 0; + } + + free(rkey); + + nad_free(nad); + + /* decrement outstanding verify counter */ + --out->verify; + + /* let them know what happened */ + nad = nad_new(in->s->nad_cache); + + ns = nad_add_namespace(nad, uri_DIALBACK, "db"); + nad_append_elem(nad, ns, "result", 0); + nad_append_attr(nad, -1, "to", from->domain); + nad_append_attr(nad, -1, "from", to->domain); + nad_append_attr(nad, -1, "type", valid ? "valid" : "invalid"); + + /* off it goes */ + sx_nad_write(in->s, nad); + + /* if invalid, close the stream */ + if (!valid) { + /* generate stream error */ + sx_error(in->s, stream_err_INVALID_ID, "dialback negotiation failed"); + + /* close the incoming stream */ + sx_close(in->s); + } + + jid_free(from); + jid_free(to); +} + +/* bounce all packets in the queue for domain */ +int out_bounce_queue(s2s_t s2s, const char *domain, int err) +{ + jqueue_t q; + pkt_t pkt; + int pktcount = 0; + + q = xhash_get(s2s->outq, domain); + if(q == NULL) + return 0; + + while((pkt = jqueue_pull(q)) != NULL) { + if(pkt->nad->ecur > 1 && NAD_NURI_L(pkt->nad, NAD_ENS(pkt->nad, 1)) == strlen(uri_CLIENT) && strncmp(NAD_NURI(pkt->nad, NAD_ENS(pkt->nad, 1)), uri_CLIENT, strlen(uri_CLIENT)) == 0) { + sx_nad_write(s2s->router, stanza_tofrom(stanza_tofrom(stanza_error(pkt->nad, 1, err), 1), 0)); + pktcount++; + } + else + nad_free(pkt->nad); + + jid_free(pkt->to); + jid_free(pkt->from); + free(pkt); + } + + return pktcount; +} + +int out_bounce_conn_queues(conn_t out, int err) +{ + char *c; + char *rkey; + + /* bounce queues for all domains handled by this connection - iterate through routes */ + if (xhash_iter_first(out->routes)) { + do { + xhash_iter_get(out->routes, (const char **) &rkey, NULL); + c = strchr(rkey, '/'); + c++; + out_bounce_queue(out->s2s, c, err); + } while(xhash_iter_next(out->routes)); + } + + return 0; +} diff --git a/s2s/router.c b/s2s/router.c new file mode 100644 index 00000000..51743dde --- /dev/null +++ b/s2s/router.c @@ -0,0 +1,320 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "s2s.h" + +/** our master callback */ +int s2s_router_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + s2s_t s2s = (s2s_t) arg; + sx_buf_t buf = (sx_buf_t) data; + sx_error_t *sxe; + nad_t nad; + int len, ns, elem, attr; + pkt_t pkt; + + switch(e) { + case event_WANT_READ: + log_debug(ZONE, "want read"); + mio_read(s2s->mio, s2s->fd); + break; + + case event_WANT_WRITE: + log_debug(ZONE, "want write"); + mio_write(s2s->mio, s2s->fd); + break; + + case event_READ: + log_debug(ZONE, "reading from %d", s2s->fd); + + /* do the read */ + len = recv(s2s->fd, buf->data, buf->len, 0); + + if(len < 0) { + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) { + buf->len = 0; + return 0; + } + + log_write(s2s->log, LOG_NOTICE, "[%d] [router] read error: %s (%d)", s2s->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + } + + else if(len == 0) { + /* they went away */ + sx_kill(s); + + return -1; + } + + log_debug(ZONE, "read %d bytes", len); + + buf->len = len; + + return len; + + case event_WRITE: + log_debug(ZONE, "writing to %d", s2s->fd); + + len = send(s2s->fd, buf->data, buf->len, 0); + if(len >= 0) { + log_debug(ZONE, "%d bytes written", len); + return len; + } + + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) + return 0; + + log_write(s2s->log, LOG_NOTICE, "[%d] [router] write error: %s (%d)", s2s->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + + case event_ERROR: + sxe = (sx_error_t *) data; + log_write(s2s->log, LOG_NOTICE, "error from router: %s (%s)", sxe->generic, sxe->specific); + + if(sxe->code == SX_ERR_AUTH) + sx_close(s); + + break; + + case event_STREAM: + break; + + case event_OPEN: + log_write(s2s->log, LOG_NOTICE, "connection to router established"); + + /* reset connection attempts counter */ + s2s->retry_left = s2s->retry_init; + + nad = nad_new(s2s->router->nad_cache); + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, ns, "bind", 0); + nad_append_attr(nad, -1, "name", s2s->id); + if(s2s->router_default) + nad_append_elem(nad, ns, "default", 1); + + log_debug(ZONE, "requesting component bind for '%s'", s2s->id); + + sx_nad_write(s2s->router, nad); + + break; + + case event_PACKET: + nad = (nad_t) data; + + /* drop unqualified packets */ + if(NAD_ENS(nad, 0) < 0) { + nad_free(nad); + return 0; + } + + /* watch for the features packet */ + if(s->state == state_STREAM) { + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_STREAMS) || strncmp(uri_STREAMS, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_STREAMS)) != 0 || NAD_ENAME_L(nad, 0) != 8 || strncmp("features", NAD_ENAME(nad, 0), 8) != 0) { + log_debug(ZONE, "got a non-features packet on an unauth'd stream, dropping"); + nad_free(nad); + return 0; + } + +#ifdef HAVE_SSL + /* starttls if we can */ + if(s2s->sx_ssl != NULL && s2s->router_pemfile != NULL && s->ssf == 0) { + ns = nad_find_scoped_namespace(nad, uri_TLS, NULL); + if(ns >= 0) { + elem = nad_find_elem(nad, 0, ns, "starttls", 1); + if(elem >= 0) { + if(sx_ssl_client_starttls(s2s->sx_ssl, s, s2s->router_pemfile) == 0) { + nad_free(nad); + return 0; + } + log_write(s2s->log, LOG_NOTICE, "unable to establish encrypted session with router"); + } + } + } +#endif + + /* !!! pull the list of mechanisms, and choose the best one. + * if there isn't an appropriate one, error and bail */ + + /* authenticate */ + sx_sasl_auth(s2s->sx_sasl, s, "jabberd-router", "DIGEST-MD5", s2s->router_user, s2s->router_pass); + + nad_free(nad); + return 0; + } + + /* watch for the bind response */ + if(s->state == state_OPEN && !s2s->online) { + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_COMPONENT) || strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) != 0 || NAD_ENAME_L(nad, 0) != 4 || strncmp("bind", NAD_ENAME(nad, 0), 4)) { + log_debug(ZONE, "got a packet from router, but we're not online, dropping"); + nad_free(nad); + return 0; + } + + /* catch errors */ + attr = nad_find_attr(nad, 0, -1, "error", NULL); + if(attr >= 0) { + log_write(s2s->log, LOG_NOTICE, "router refused bind request (%.*s)", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + exit(1); + } + + log_debug(ZONE, "coming online"); + + /* if we're coming online for the first time, setup listening sockets */ + if(s2s->server_fd == 0) { + if(s2s->local_port != 0) { + s2s->server_fd = mio_listen(s2s->mio, s2s->local_port, s2s->local_ip, in_mio_callback, (void *) s2s); + if(s2s->server_fd < 0) { + log_write(s2s->log, LOG_ERR, "[%s, port=%d] failed to listen", s2s->local_ip, s2s->local_port); + exit(1); + } else + log_write(s2s->log, LOG_NOTICE, "[%s, port=%d] listening for connections", s2s->local_ip, s2s->local_port); + } + } + + /* we're online */ + s2s->online = s2s->started = 1; + log_write(s2s->log, LOG_NOTICE, "ready for connections", s2s->id); + + nad_free(nad); + return 0; + } + + log_debug(ZONE, "got a packet"); + + /* sanity checks */ + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_COMPONENT) || strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) != 0) { + log_debug(ZONE, "unknown namespace, dropping packet"); + nad_free(nad); + return 0; + } + + if(NAD_ENAME_L(nad, 0) != 5 || strncmp("route", NAD_ENAME(nad, 0), 5) != 0) { + log_debug(ZONE, "dropping non-route packet"); + nad_free(nad); + return 0; + } + + if(nad_find_attr(nad, 0, -1, "error", NULL) >= 0) { + /* !!! catch failures to route to the resolver, and bounce outgoing packets */ + /* !!! jabber:client packets and error=404 means the dest component wasn't found, bounce them to the sender */ + log_debug(ZONE, "dropping route error packet"); + nad_free(nad); + return 0; + } + + if(nad_find_attr(nad, 0, -1, "type", NULL) >= 0) { + log_debug(ZONE, "dropping non-unicast packet"); + nad_free(nad); + return 0; + } + + /* packets to us */ + attr = nad_find_attr(nad, 0, -1, "to", NULL); + if(NAD_AVAL_L(nad, attr) == strlen(s2s->id) && strncmp(s2s->id, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)) == 0) { + + if(nad->ecur >= 2 && + NAD_NURI_L(nad, NAD_ENS(nad, 1)) == strlen(uri_RESOLVER) && strncmp(uri_RESOLVER, NAD_NURI(nad, NAD_ENS(nad, 1)), strlen(uri_RESOLVER)) == 0 && + NAD_ENAME_L(nad, 1) == 7 && strncmp("resolve", NAD_ENAME(nad, 1), 7) == 0) { + /* resolver response */ + out_resolve(s2s, nad); + return 0; + } + + log_debug(ZONE, "dropping unknown or invalid packet for s2s component proper"); + nad_free(nad); + + return 0; + } + + /* new packet */ + pkt = (pkt_t) malloc(sizeof(struct pkt_st)); + memset(pkt, 0, sizeof(struct pkt_st)); + + pkt->nad = nad; + + attr = nad_find_attr(nad, 0, -1, "from", NULL); + pkt->from = jid_new(s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + attr = nad_find_attr(nad, 0, -1, "to", NULL); + pkt->to = jid_new(s2s->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + /* change the packet so it looks like it came to us, so the router won't reject it if we bounce it later */ + nad_set_attr(nad, 0, -1, "to", s2s->id, 0); + + /* flag dialback */ + if(NAD_NURI_L(pkt->nad, 0) == uri_DIALBACK_L && strncmp(uri_DIALBACK, NAD_NURI(pkt->nad, 0), uri_DIALBACK_L) == 0) + pkt->db = 1; + + /* send it out */ + out_packet(s2s, pkt); + + return 0; + + case event_CLOSED: + mio_close(s2s->mio, s2s->fd); + break; + } + + return 0; +} + +int s2s_router_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) { + s2s_t s2s = (s2s_t) arg; + int nbytes; + + switch(a) { + case action_READ: + log_debug(ZONE, "read action on fd %d", fd); + + ioctl(fd, FIONREAD, &nbytes); + if(nbytes == 0) { + sx_kill(s2s->router); + return 0; + } + + return sx_can_read(s2s->router); + + case action_WRITE: + log_debug(ZONE, "write action on fd %d", fd); + return sx_can_write(s2s->router); + + case action_CLOSE: + log_debug(ZONE, "close action on fd %d", fd); + log_write(s2s->log, LOG_NOTICE, "connection to router closed"); + + s2s_lost_router = 1; + + /* we're offline */ + s2s->online = 0; + + break; + + case action_ACCEPT: + break; + } + + return 0; +} diff --git a/s2s/s2s.h b/s2s/s2s.h new file mode 100644 index 00000000..73a27e67 --- /dev/null +++ b/s2s/s2s.h @@ -0,0 +1,250 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "mio/mio.h" +#include "sx/sx.h" +#include "sx/ssl.h" +#include "sx/sasl.h" + +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif + +/* forward decl */ +typedef struct s2s_st *s2s_t; +typedef struct pkt_st *pkt_t; +typedef struct conn_st *conn_t; +typedef struct dnscache_st *dnscache_t; + +struct s2s_st { + /** our id (hostname) with the router */ + char *id; + + /** how to connect to the router */ + char *router_ip; + int router_port; + char *router_user; + char *router_pass; + char *router_pemfile; + int router_default; + + /** mio context */ + mio_t mio; + + /** sx environment */ + sx_env_t sx_env; + sx_plugin_t sx_ssl; + sx_plugin_t sx_sasl; + sx_plugin_t sx_db; + + /** router's conn */ + sx_t router; + int fd; + + /** listening sockets */ + int server_fd; + + /** config */ + config_t config; + + /** logging */ + log_t log; + + /** log data */ + log_type_t log_type; + char *log_facility; + char *log_ident; + + /** connect retry */ + int retry_init; + int retry_lost; + int retry_sleep; + int retry_left; + + /** ip/port to listen on */ + char *local_ip; + int local_port; + + /** id of resolver */ + char *local_resolver; + + /** dialback secret */ + char *local_secret; + + /** pemfile for peer connections */ + char *local_pemfile; + + /** certificate chain */ + char *local_cachain; + + /** verify-mode */ + int local_verify_mode; + + /** time checks */ + int check_interval; + int check_queue; + int check_invalid; + int check_keepalive; + int check_idle; + + time_t last_queue_check; + time_t last_invalid_check; + + time_t next_check; + + /** stringprep cache */ + prep_cache_t pc; + + /** list of sx_t on the way out */ + jqueue_t dead; + + /** list of conn_t on the way out */ + jqueue_t dead_conn; + + /** this is true if we've connected to the router at least once */ + int started; + + /** true if we're bound in the router */ + int online; + + /** queues of packets waiting to go out (key is dest domain) */ + xht outq; + + /** outgoing conns (key is ip/port) */ + xht out; + + /** incoming conns (key is stream id) */ + xht in; + + /** incoming conns prior to stream initiation (key is ip/port) */ + xht in_accept; + + /** dns resolution cache */ + xht dnscache; +}; + +struct pkt_st { + nad_t nad; + + jid_t from; + jid_t to; + + int db; + + char ip[INET6_ADDRSTRLEN]; + int port; +}; + +typedef enum { + conn_NONE, + conn_INPROGRESS, + conn_VALID, + conn_INVALID +} conn_state_t; + +struct conn_st { + s2s_t s2s; + + char *key; + + sx_t s; + int fd; + + char ip[INET6_ADDRSTRLEN]; + int port; + + /** states of outgoing dialbacks (key is local/remote) */ + xht states; + + /** time of the last state change (key is local/remote) */ + xht states_time; + + /** routes that this conn handles (key is local/remote) */ + xht routes; + + time_t init_time; + + int online; + + /** number and last timestamp of outstanding db:verify requests */ + int verify; + time_t last_verify; + + /** timestamps for idle timeouts */ + time_t last_activity; + time_t last_packet; +}; + +/** one item in the dns resolution cache */ +struct dnscache_st { + /** the name proper */ + char name[1024]; + + /** ip and port that the name resolves to */ + char ip[INET6_ADDRSTRLEN]; + int port; + + /** time that this entry expires */ + time_t expiry; + + time_t init_time; + + /** set when we're waiting for a resolve response */ + int pending; +}; + +extern sig_atomic_t s2s_lost_router; + +int s2s_router_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg); +int s2s_router_sx_callback(sx_t s, sx_event_t e, void *data, void *arg); + +char *s2s_route_key(pool p, char *local, char *remote); +char *s2s_db_key(pool p, char *secret, char *remote, char *id); + +void out_packet(s2s_t s2s, pkt_t pkt); +void out_resolve(s2s_t s2s, nad_t nad); +void out_dialback(s2s_t s2s, pkt_t pkt); +int out_bounce_queue(s2s_t s2s, const char *domain, int err); +int out_bounce_conn_queues(conn_t out, int err); + +int in_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg); + +/* sx flag for outgoing dialback streams */ +#define S2S_DB_HEADER (1<<10) + +int s2s_db_init(sx_env_t env, sx_plugin_t p, va_list args); + +/* union for xhash_iter_get to comply with strict-alias rules for gcc3 */ +union xhashv +{ + void **val; + char **char_val; + conn_t *conn_val; + conn_state_t *state_val; + jqueue_t *jq_val; + dnscache_t *dns_val; +}; diff --git a/s2s/sx.c b/s2s/sx.c new file mode 100644 index 00000000..a452b68c --- /dev/null +++ b/s2s/sx.c @@ -0,0 +1,52 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* + * this is a minimal sx plugin that hacks the "jabber:server:dialback" + * onto outgoing connections + */ + +#include "s2s.h" + +#define S2S_DB_NS_DECL " xmlns:db='" uri_DIALBACK "'" +#define S2S_DB_NS_DECL_LEN (strlen(uri_DIALBACK) + 12) + +static void _s2s_db_header(sx_t s, sx_plugin_t p, sx_buf_t buf) { + + if(!(s->flags & S2S_DB_HEADER)) + return; + + log_debug(ZONE, "hacking dialback namespace decl onto stream header"); + + /* get enough space */ + _sx_buffer_alloc_margin(buf, 0, S2S_DB_NS_DECL_LEN + 2); + + /* overwrite the trailing ">" with a decl followed by a new ">" */ + memcpy(&buf->data[buf->len - 1], S2S_DB_NS_DECL ">", S2S_DB_NS_DECL_LEN+1); + buf->len += S2S_DB_NS_DECL_LEN; +} + +int s2s_db_init(sx_env_t env, sx_plugin_t p, va_list args) { + log_debug(ZONE, "initialising dialback sx plugin"); + + p->header = _s2s_db_header; + + return 0; +} diff --git a/s2s/util.c b/s2s/util.c new file mode 100644 index 00000000..6884fcce --- /dev/null +++ b/s2s/util.c @@ -0,0 +1,57 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "s2s.h" + +/** generate a local/remote route key */ +char *s2s_route_key(pool p, char *local, char *remote) { + char *key; + + if(p == NULL) + key = (char *) malloc(strlen(local) + strlen(remote) + 2); + else + key = (char *) pmalloc(p, strlen(local) + strlen(remote) + 2); + + sprintf(key, "%s/%s", local, remote); + + return key; +} + +/** generate a dialback key */ +char *s2s_db_key(pool p, char *secret, char *remote, char *id) { + char hash[41], buf[1024]; + + _sx_debug(ZONE, "generating dialback key, secret %s, remote %s, id %s", secret, remote, id); + + shahash_r(secret, hash); + + snprintf(buf, 1024, "%s%s", hash, remote); + shahash_r(buf, hash); + + snprintf(buf, 1024, "%s%s", hash, id); + shahash_r(buf, hash); + + _sx_debug(ZONE, "dialback key generated: %s", hash); + + if(p == NULL) + return strdup(hash); + else + return pstrdup(p, hash); +} diff --git a/scod/.cvsignore b/scod/.cvsignore new file mode 100644 index 00000000..6e5ca7ed --- /dev/null +++ b/scod/.cvsignore @@ -0,0 +1,6 @@ +Makefile +Makefile.in +.deps +.libs +*.lo +*.la diff --git a/scod/Makefile.am b/scod/Makefile.am new file mode 100644 index 00000000..205b4354 --- /dev/null +++ b/scod/Makefile.am @@ -0,0 +1,6 @@ +noinst_LTLIBRARIES = libscod.la + +noinst_HEADERS = scod.h + +libscod_la_SOURCES = mech_anonymous.c mech_plain.c mech_digest_md5.c scod.c +libscod_la_LIBADD = @LDFLAGS@ diff --git a/scod/mech_anonymous.c b/scod/mech_anonymous.c new file mode 100644 index 00000000..4f42b7e8 --- /dev/null +++ b/scod/mech_anonymous.c @@ -0,0 +1,54 @@ +/* + * scod - a minimal sasl implementation for jabberd2 + * Copyright (c) 2003 Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* ANONYMOUS mechanism */ + +#include "scod.h" + +static int _anonymous_client_start(scod_mech_t mech, scod_t sd, char **resp, int *resplen) { + log_debug(ZONE, "ANONYMOUS client start"); + + return sd_SUCCESS; +} + +static int _anonymous_server_start(scod_mech_t mech, scod_t sd, const char *resp, int resplen, char **chal, int *challen) { + char authzid[3072]; + + log_debug(ZONE, "ANONYMOUS server start"); + + if((mech->ctx->cb)(sd, sd_cb_ANONYMOUS_GEN_AUTHZID, NULL, (void **) authzid, mech->ctx->cbarg) != 0) { + log_debug(ZONE, "app failed to generate authzid, auth failed"); + return sd_auth_AUTH_FAILED; + } + + sd->authzid = strdup(authzid); + + return sd_SUCCESS; +} + +int scod_mech_anonymous_init(scod_mech_t mech) { + log_debug(ZONE, "initialising ANONYMOUS mechanism"); + + mech->name = "ANONYMOUS"; + + mech->client_start = _anonymous_client_start; + mech->server_start = _anonymous_server_start; + + return 0; +} diff --git a/scod/mech_digest_md5.c b/scod/mech_digest_md5.c new file mode 100644 index 00000000..2705c78f --- /dev/null +++ b/scod/mech_digest_md5.c @@ -0,0 +1,699 @@ +/* + * scod - a minimal sasl implementation for jabberd2 + * Copyright (c) 2003 Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* DIGEST-MD5 mechanism */ + +#include "scod.h" + +#include + +#define HT (9) +#define CR (13) +#define LF (10) +#define SP (32) +#define DEL (127) + +/* unions to comply with strict-alias rules for gcc3 */ +union xhashv +{ + void **val; + xht *xht_val; +}; + +union scod_u +{ + void **val; + char **char_val; +}; + +static char *_opt_quote(char *in) { + int nesc; + char *r, *out, *w; + + r = in; + nesc = 0; + while(*r != '\0') { + if(*r == '"' || *r == '\\') + nesc++; + r++; + } + + out = (char *) malloc(sizeof(char) * (strlen(in) + nesc + 3)); + + r = in; + w = out; + + *w = '"'; + w++; + while(*r != '\0') { + if(*r == '"' || *r == '\\') { + *w = '\\'; + w++; + } + *w = *r; + w++; + r++; + } + + *w = '"'; + w++; + *w = '\0'; + + log_debug(ZONE, "escaped '%s' into '%s'", in, out); + + return out; +} + +/** the list parser is based on code from cyrus-sasl. I love open source ;) */ +static char *_opt_skip_lws(char *c) { + if(c == NULL) + return NULL; + + while(*c == ' ' || *c == HT || *c == CR || *c == LF) { + if(*c == '\0') + break; + c++; + } + + return c; +} + +static char *_opt_skip_token(char *c, int ci) { + if(c == NULL) + return NULL; + + while(*c > SP) { + if(*c == DEL || *c == '(' || *c == ')' || *c == '<' || *c == '>' || + *c == '@' || *c == ',' || *c == ';' || *c == ':' || *c == '\\' || + *c == '\'' || *c == '/' || *c == '[' || *c == ']' || *c == '?' || + *c == '=' || *c == '{' || *c == '}') { + if(ci) { + if(!isupper((unsigned char) *c)) + break; + } else + break; + } + c++; + } + + return c; +} + +static char *_opt_unquote(char *in) { + char *out, *end; + int esc = 0; + + /* if its not quoted, there's nothing to do */ + if(*in != '"') + return _opt_skip_token(in, 0); + + in++; + out = in; + + for(end = in; *end != '\0'; end++, out++) { + if(esc) { + *out = *end; + esc = 0; + } + else if(*end == '\\') { + esc = 1; + out--; + } + else if(*end == '"') + break; + else + *out = *end; + } + + if(*end != '"') + return NULL; + + while(out <= end) { + *out = '\0'; + out++; + } + + end++; + + return end; +} + +static void _opt_get_pair(char **in, char **key, char **val) { + char *end, *cur = *in; + + *key = NULL; + *val = NULL; + + if(*cur == '\0') + return; + + cur = _opt_skip_lws(cur); + + *key = cur; + + cur = _opt_skip_token(cur, 1); + + if(*cur != '=' && *cur != '\0') { + *cur = '\0'; + cur++; + } + + cur = _opt_skip_lws(cur); + + if(*cur != '=') { + *key = NULL; + return; + } + + *cur = '\0'; + cur++; + + cur = _opt_skip_lws(cur); + + *val = (*cur == '"') ? cur + 1 : cur; + + end = _opt_unquote(cur); + if(end == NULL) { + *key = NULL; + return; + } + + if(*end != ',' && *end != '\0') { + *end = '\0'; + end++; + } + + end = _opt_skip_lws(end); + + if(*end == ',') { + *end = '\0'; + end++; + } + else if(*end != '\0') { + *key = NULL; + return; + } + + *in = end; +} + +static xht _digest_md5_parse_options(const char *buf, int buflen) { + xht hash, sub; + char *nbuf, *in, *key, *val; + + nbuf = (char *) malloc(sizeof(char) * (buflen + 1)); + strncpy(nbuf, buf, buflen); + nbuf[buflen] = '\0'; + + hash = xhash_new(101); + + in = nbuf; + while(1) { + _opt_get_pair(&in, &key, &val); + if(key == NULL) + break; + + sub = xhash_get(hash, key); + if(sub == NULL) { + sub = xhash_new(11); + xhash_put(hash, pstrdup(xhash_pool(hash), key), sub); + pool_cleanup(xhash_pool(hash), (void (*)(void *)) xhash_free, sub); + } + + xhash_put(sub, pstrdup(xhash_pool(hash), val), (void *) 1); + + log_debug(ZONE, "got key '%s' val '%s'", key, val); + } + + free(nbuf); + + return hash; +} + +static char *_digest_md5_gen_nonce(void) { + int i, r; + char nonce[65], hnonce[41]; + + for(i = 0; i < 64; i++) { + r = (int) (36.0 * rand() / RAND_MAX); + nonce[i] = (r >= 0 && r <= 9) ? (r + 48) : (r + 87); + } + nonce[64] = '\0'; + + shahash_r(nonce, hnonce); + + log_debug(ZONE, "generated nonce: %s", hnonce); + + return strdup(hnonce); +} + +typedef struct _digest_md5_st { + pool p; + + char *nonce; + char *cnonce; + char *nc; + + int step; +} *digest_md5_t; + +static int _digest_md5_client_start(scod_mech_t mech, scod_t sd, char **resp, int *resplen) { + log_debug(ZONE, "DIGEST-MD5 client start"); + + return sd_CONTINUE; +} + +static int _digest_md5_client_step(scod_mech_t mech, scod_t sd, const char *chal, int challen, char **resp, int *resplen) { + xht attrs, sub; + char *key, *realm, *nonce, *qop, *charset, *algorithm, *cnonce, *c; + md5_state_t md5; + md5_byte_t hash[16]; + char ha1[33], ha2[33], hrsp[33]; + pool p; + spool s; + union xhashv xhv; + union scod_u su; + + log_debug(ZONE, "DIGEST-MD5 client step; challenge: %.*s", challen, chal); + + if(sd->mech_data != NULL) { + /* !!! check rspauth */ + sd->mech_data = NULL; + return sd_SUCCESS; + } + + realm = nonce = qop = charset = algorithm = NULL; + + attrs = _digest_md5_parse_options(chal, challen); + if(xhash_iter_first(attrs)) + do { + xhv.xht_val = ⊂ + xhash_iter_get(attrs, (const char **) &key, xhv.val); + log_debug(ZONE, "extracting '%s'", key); + + if(xhash_iter_first(sub)) { + if(strcmp(key, "realm") == 0) { + su.char_val = &realm; + (mech->ctx->cb)(sd, sd_cb_DIGEST_MD5_CHOOSE_REALM, (void *) sub, su.val, mech->ctx->cbarg); + } + else if(strcmp(key, "nonce") == 0) + xhash_iter_get(sub, (const char **) &nonce, NULL); + else if(strcmp(key, "qop") == 0) + xhash_iter_get(sub, (const char **) &qop, NULL); + else if(strcmp(key, "charset") == 0) + xhash_iter_get(sub, (const char **) &charset, NULL); + else if(strcmp(key, "algorithm") == 0) + xhash_iter_get(sub, (const char **) &algorithm, NULL); + } + } while(xhash_iter_next(attrs)); + + if(nonce == NULL || qop == NULL || charset == NULL || algorithm == NULL) { + log_debug(ZONE, "missing attribute"); + xhash_free(attrs); + return sd_auth_MALFORMED_DATA; + } + + cnonce = _digest_md5_gen_nonce(); + + md5_init(&md5); + md5_append(&md5, sd->authnid, strlen(sd->authnid)); + md5_append(&md5, ":", 1); + if(realm != NULL) md5_append(&md5, realm, strlen(realm)); + md5_append(&md5, ":", 1); + md5_append(&md5, sd->pass, strlen(sd->pass)); + md5_finish(&md5, hash); + + md5_init(&md5); + md5_append(&md5, hash, 16); + md5_append(&md5, ":", 1); + md5_append(&md5, nonce, strlen(nonce)); + md5_append(&md5, ":", 1); + md5_append(&md5, cnonce, 40); + if(sd->authzid != NULL) { + md5_append(&md5, ":", 1); + md5_append(&md5, sd->authzid, strlen(sd->authzid)); + } + md5_finish(&md5, hash); /* A1 */ + + hex_from_raw(hash, 16, ha1); + + log_debug(ZONE, "HEX(H(A1)) = %s", ha1); + + md5_init(&md5); + md5_append(&md5, "AUTHENTICATE:", 13); + md5_append(&md5, "xmpp/", 5); /* !!! make this configurable */ + md5_finish(&md5, hash); /* A2 */ + + hex_from_raw(hash, 16, ha2); + + log_debug(ZONE, "HEX(H(A2)) = %s", ha2); + + md5_init(&md5); + md5_append(&md5, ha1, 32); + md5_append(&md5, ":", 1); + md5_append(&md5, nonce, strlen(nonce)); + md5_append(&md5, ":", 1); + md5_append(&md5, "00000001", 8); + md5_append(&md5, ":", 1); + md5_append(&md5, cnonce, 40); + md5_append(&md5, ":auth:", 6); + md5_append(&md5, ha2, 32); + md5_finish(&md5, hash); /* KD(HA1, foo, HA2) */ + + hex_from_raw(hash, 16, hrsp); + + log_debug(ZONE, "response is %s", hrsp); + + /* !!! generate rspauth and save it for later so we can validate */ + + p = pool_new(); + s = spool_new(p); + + c = _opt_quote(sd->authnid); + spooler(s, "username=", c, ",", s); + free(c); + + c = _opt_quote(nonce); + spooler(s, "nonce=", c, ",", s); + free(c); + + c = _opt_quote(cnonce); + spooler(s, "cnonce=", c, ",", s); + free(c); + + if(sd->authzid != NULL) { + c = _opt_quote(sd->authzid); + spooler(s, "authzid=", c, ",", s); + free(c); + } + + if(realm != NULL) { + c = _opt_quote(realm); + spooler(s, "realm=", c, ",", s); + free(c); + } + + spooler(s, "nc=00000001,qop=auth,digest-uri=\"xmpp/\",charset=utf-8,response=", hrsp, s); + + *resp = strdup(spool_print(s)); + *resplen = strlen(*resp); + + pool_free(p); + xhash_free(attrs); + + free(cnonce); + + log_debug(ZONE, "generated initial response: %.*s", *resplen, *resp); + + sd->mech_data = (void *) 1; + + return sd_CONTINUE; +} + +static int _digest_md5_server_start(scod_mech_t mech, scod_t sd, const char *resp, int resplen, char **chal, int *challen) { + digest_md5_t md; + pool p; + spool s; + char *c, *nonce; + + log_debug(ZONE, "DIGEST-MD5 server start"); + + p = pool_new(); + md = (digest_md5_t) pmalloco(p, sizeof(struct _digest_md5_st)); + md->p = p; + sd->mech_data = md; + + p = pool_new(); + s = spool_new(p); + + if(sd->realm != NULL) { + c = _opt_quote(sd->realm); + spooler(s, "realm=", c, ",", s); + free(c); + } + + nonce = _digest_md5_gen_nonce(); + md->nonce = pstrdup(md->p, nonce); + free(nonce); + + c = _opt_quote(md->nonce); + spooler(s, "nonce=", c, ",qop=\"auth\",charset=utf-8,algorithm=md5-sess", s); + free(c); + + *chal = strdup(spool_print(s)); + *challen = strlen(*chal); + + pool_free(p); + + log_debug(ZONE, "generated initial challenge: %.*s", *challen, *chal); + + return sd_CONTINUE; +} + +static int _digest_md5_server_step(scod_mech_t mech, scod_t sd, const char *resp, int resplen, char **chal, int *challen) { + digest_md5_t md = (digest_md5_t) sd->mech_data; + xht attrs, sub; + char *key, *username, *realm, *nonce, *cnonce, *nc, *qop, *digest_uri, *response, *charset, *pass, buf[257], *c, authzid[3072]; + int err; + md5_state_t md5; + md5_byte_t hash[16]; + char ha1[33], ha2[33], hrsp[33]; + struct _scod_cb_creds_st creds; + union xhashv xhv; + union scod_u su; + + log_debug(ZONE, "DIGEST-MD5 server step; response: %.*s", resplen, resp); + + if(md->step == 1) { + /* we're done */ + pool_free(md->p); + sd->mech_data = NULL; + return sd_SUCCESS; + } + + username = realm = nonce = cnonce = nc = qop = digest_uri = response = charset = NULL; + authzid[0] = '\0'; + + attrs = _digest_md5_parse_options(resp, resplen); + if(xhash_iter_first(attrs)) + do { + xhv.xht_val = ⊂ + xhash_iter_get(attrs, (const char **) &key, xhv.val); + log_debug(ZONE, "extracting '%s'", key); + + if(xhash_iter_first(sub)) { + if(strcmp(key, "username") == 0) + xhash_iter_get(sub, (const char **) &username, NULL); + else if(strcmp(key, "realm") == 0) + xhash_iter_get(sub, (const char **) &realm, NULL); + else if(strcmp(key, "nonce") == 0) + xhash_iter_get(sub, (const char **) &nonce, NULL); + else if(strcmp(key, "cnonce") == 0) + xhash_iter_get(sub, (const char **) &cnonce, NULL); + else if(strcmp(key, "nc") == 0) + xhash_iter_get(sub, (const char **) &nc, NULL); + else if(strcmp(key, "qop") == 0) + xhash_iter_get(sub, (const char **) &qop, NULL); + else if(strcmp(key, "digest-uri") == 0) + xhash_iter_get(sub, (const char **) &digest_uri, NULL); + else if(strcmp(key, "response") == 0) + xhash_iter_get(sub, (const char **) &response, NULL); + else if(strcmp(key, "charset") == 0) + xhash_iter_get(sub, (const char **) &charset, NULL); + else if(strcmp(key, "authzid") == 0) { + xhash_iter_get(sub, (const char **) &c, NULL); + strncpy(authzid, c, sizeof(authzid)); + } + } + } while(xhash_iter_next(attrs)); + + err = sd_SUCCESS; + if(username == NULL || nonce == NULL || cnonce == NULL || nc == NULL || qop == NULL || digest_uri == NULL || response == NULL) + err = sd_auth_MALFORMED_DATA; + else if(strcmp(nonce, md->nonce) != 0) + err = sd_auth_MISMATCH; + else if(strcmp(qop, "auth") != 0) + err = sd_auth_NOT_OFFERED; + + if(err != sd_SUCCESS) { + log_debug(ZONE, "returning error %d", err); + + xhash_free(attrs); + pool_free(md->p); + sd->mech_data = NULL; + + return err; + } + + /* !!! verify realm? */ + + creds.authnid = username; + creds.realm = realm; + creds.pass = NULL; + pass = buf; + su.char_val = &pass; + if((mech->ctx->cb)(sd, sd_cb_GET_PASS, &creds, su.val, mech->ctx->cbarg) != 0) { + log_debug(ZONE, "user not found (or some other error getting password), failing"); + + xhash_free(attrs); + pool_free(md->p); + sd->mech_data = NULL; + + return sd_auth_USER_UNKNOWN; + } + + md->cnonce = pstrdup(md->p, cnonce); + md->nc = pstrdup(md->p, nc); + + md5_init(&md5); + md5_append(&md5, username, strlen(username)); + md5_append(&md5, ":", 1); + if(realm != NULL) md5_append(&md5, realm, strlen(realm)); + md5_append(&md5, ":", 1); + if(pass != NULL) md5_append(&md5, pass, strlen(pass)); + md5_finish(&md5, hash); + + md5_init(&md5); + md5_append(&md5, hash, 16); + md5_append(&md5, ":", 1); + md5_append(&md5, md->nonce, strlen(md->nonce)); + md5_append(&md5, ":", 1); + md5_append(&md5, md->cnonce, strlen(md->cnonce)); + if(authzid[0] != '\0') { + md5_append(&md5, ":", 1); + md5_append(&md5, authzid, strlen(authzid)); + } + md5_finish(&md5, hash); /* A1 */ + + hex_from_raw(hash, 16, ha1); + + log_debug(ZONE, "HEX(H(A1)) = %s", ha1); + + md5_init(&md5); + md5_append(&md5, "AUTHENTICATE:", 13); + md5_append(&md5, digest_uri, strlen(digest_uri)); + md5_finish(&md5, hash); /* A2 */ + + hex_from_raw(hash, 16, ha2); + + log_debug(ZONE, "HEX(H(A2)) = %s", ha2); + + md5_init(&md5); + md5_append(&md5, ha1, 32); + md5_append(&md5, ":", 1); + md5_append(&md5, nonce, strlen(nonce)); + md5_append(&md5, ":", 1); + md5_append(&md5, nc, strlen(nc)); + md5_append(&md5, ":", 1); + md5_append(&md5, cnonce, strlen(cnonce)); + md5_append(&md5, ":auth:", 6); + md5_append(&md5, ha2, 32); + md5_finish(&md5, hash); /* KD(HA1, foo, HA2) */ + + hex_from_raw(hash, 16, hrsp); + + log_debug(ZONE, "our response is %s, theirs is %s", hrsp, response); + + if(strcmp(hrsp, response) != 0) { + log_debug(ZONE, "not matched, denied"); + + xhash_free(attrs); + pool_free(md->p); + sd->mech_data = NULL; + + return sd_auth_AUTH_FAILED; + } + + log_debug(ZONE, "matched, they're authenticated"); + + creds.authnid = username; + creds.realm = realm; + creds.pass = NULL; + + creds.authzid = authzid; + + if((mech->ctx->cb)(sd, sd_cb_CHECK_AUTHZID, &creds, NULL, mech->ctx->cbarg) != 0) { + log_debug(ZONE, "authzid is invalid (app policy said so)"); + + xhash_free(attrs); + pool_free(md->p); + sd->mech_data = NULL; + + return sd_auth_AUTHZID_POLICY; + } + + sd->authzid = strdup(creds.authzid); + + md5_init(&md5); + md5_append(&md5, ":", 1); + md5_append(&md5, digest_uri, strlen(digest_uri)); + md5_finish(&md5, hash); /* rspauth A2 */ + + hex_from_raw(hash, 16, ha2); + + log_debug(ZONE, "HEX(H(rspauth A2)) = %s", ha2); + + md5_init(&md5); + md5_append(&md5, ha1, 32); + md5_append(&md5, ":", 1); + md5_append(&md5, nonce, strlen(nonce)); + md5_append(&md5, ":", 1); + md5_append(&md5, nc, strlen(nc)); + md5_append(&md5, ":", 1); + md5_append(&md5, cnonce, strlen(cnonce)); + md5_append(&md5, ":auth:", 6); + md5_append(&md5, ha2, 32); + md5_finish(&md5, hash); /* KD(HA1, foo, HA2) */ + + hex_from_raw(hash, 16, hrsp); + + log_debug(ZONE, "rspauth: %s", hrsp); + + *chal = (char *) malloc(sizeof(char) * 41); + snprintf(*chal, 41, "rspauth=%s", hrsp); + *challen = 40; + + log_debug(ZONE, "generated final challenge: %.*s", *challen, *chal); + + md->step = 1; + + xhash_free(attrs); + + return sd_CONTINUE; +} + +static void _digest_md5_free(scod_mech_t mech) { + xhash_free((xht) mech->private); +} + +int scod_mech_digest_md5_init(scod_mech_t mech) { + log_debug(ZONE, "initialising DIGEST-MD5 mechanism"); + + mech->name = "DIGEST-MD5"; + + mech->flags = sd_flag_GET_PASS; + + mech->client_start = _digest_md5_client_start; + mech->client_step = _digest_md5_client_step; + mech->server_start = _digest_md5_server_start; + mech->server_step = _digest_md5_server_step; + mech->free = _digest_md5_free; + + return 0; +} diff --git a/scod/mech_plain.c b/scod/mech_plain.c new file mode 100644 index 00000000..e307827d --- /dev/null +++ b/scod/mech_plain.c @@ -0,0 +1,111 @@ +/* + * scod - a minimal sasl implementation for jabberd2 + * Copyright (c) 2003 Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* PLAIN mechanism */ + +#include "scod.h" + +static int _plain_client_start(scod_mech_t mech, scod_t sd, char **resp, int *resplen) { + int pos = 0; + + log_debug(ZONE, "PLAIN client start"); + + *resplen = (sd->authzid != NULL ? strlen(sd->authzid) : 0) + strlen(sd->authnid) + strlen(sd->pass) + 2; + *resp = (char *) malloc(sizeof(char) * *resplen); + + if(sd->authzid != NULL) { + snprintf(*resp, *resplen, "%s", sd->authzid); + pos = strlen(sd->authzid) + 1; + } + snprintf(&(*resp)[pos], *resplen - pos, "%s", sd->authnid); + pos += strlen(sd->authnid) + 1; + snprintf(&(*resp)[pos], *resplen - pos + 1, "%s", sd->pass); + + return sd_SUCCESS; +} + +static int _plain_server_start(scod_mech_t mech, scod_t sd, const char *resp, int resplen, char **chal, int *challen) { + char *c, authzid[3072], *authnid, *pass; + struct _scod_cb_creds_st creds; + + log_debug(ZONE, "PLAIN server start"); + + c = j_strnchr(resp, '\0', resplen); + if(c == NULL) { + log_debug(ZONE, "first null not found, this is bogus"); + return sd_auth_MALFORMED_DATA; + } + c++; + authnid = c; + + strncpy(authzid, resp, sizeof(authzid)); + + c = j_strnchr(c, '\0', resplen - (strlen(authzid) + 1)); + if(c == NULL) { + log_debug(ZONE, "second null not found, this is bogus"); + return sd_auth_MALFORMED_DATA; + } + c++; + + pass = (char *) malloc(sizeof(char) * (resplen - ((int) (c - resp)) + 1)); + strncpy(pass, c, (resplen - ((int) (c - resp)))); + pass[resplen - ((int) (c - resp))] = '\0'; + + log_debug(ZONE, "got authzid=%s, authnid=%s, pass=%s", authzid, authnid, pass); + + /* check pass */ + creds.authnid = authnid; + creds.pass = pass; + creds.realm = sd->realm; + if((mech->ctx->cb)(sd, sd_cb_CHECK_PASS, &creds, NULL, mech->ctx->cbarg) != 0) { + log_debug(ZONE, "password doesn't match, auth failed"); + free(pass); + return sd_auth_AUTH_FAILED; + } + + /* check authzid */ + creds.authnid = authnid; + creds.pass = NULL; + creds.authzid = authzid; + creds.realm = sd->realm; + if((mech->ctx->cb)(sd, sd_cb_CHECK_AUTHZID, &creds, NULL, mech->ctx->cbarg) != 0) { + log_debug(ZONE, "authzid is invalid (app policy said so)"); + free(pass); + return sd_auth_AUTHZID_POLICY; + } + + sd->authzid = strdup(authzid); + sd->authnid = strdup(authnid); + sd->pass = pass; + + return sd_SUCCESS; +} + +int scod_mech_plain_init(scod_mech_t mech) { + log_debug(ZONE, "initialising PLAIN mechanism"); + + mech->name = "PLAIN"; + + mech->flags = sd_flag_CHECK_PASS; + + mech->client_start = _plain_client_start; + mech->server_start = _plain_server_start; + + return 0; +} diff --git a/scod/scod.c b/scod/scod.c new file mode 100644 index 00000000..39129dd7 --- /dev/null +++ b/scod/scod.c @@ -0,0 +1,345 @@ +/* + * scod - a minimal sasl implementation for jabberd2 + * Copyright (c) 2003 Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "scod.h" + +extern int scod_mech_anonymous_init(scod_mech_t); +extern int scod_mech_digest_md5_init(scod_mech_t); +extern int scod_mech_plain_init(scod_mech_t); + +scod_mech_init_fn mech_inits[] = { + scod_mech_anonymous_init, + scod_mech_digest_md5_init, + scod_mech_plain_init, + NULL +}; + +scod_ctx_t scod_ctx_new(scod_callback_t cb, void *cbarg) { + int i = 0; + scod_ctx_t ctx; + scod_mech_t mech; + + assert((int) cb); + + log_debug(ZONE, "creating new scod context"); + + ctx = (scod_ctx_t) malloc(sizeof(struct _scod_ctx_st)); + memset(ctx, 0, sizeof(struct _scod_ctx_st)); + + ctx->cb = cb; + ctx->cbarg = cbarg; + + while(mech_inits[i] != NULL) { + mech = (scod_mech_t) malloc(sizeof(struct _scod_mech_st)); + memset(mech, 0, sizeof(struct _scod_mech_st)); + + mech->ctx = ctx; + + if((mech_inits[i])(mech) != sd_SUCCESS) { + log_debug(ZONE, "mech failed to init"); + free(mech); + } + + else { + ctx->mechs = (scod_mech_t *) realloc(ctx->mechs, sizeof(scod_mech_t) * (ctx->nmechs + 1)); + ctx->mechs[ctx->nmechs] = mech; + + ctx->names = (char **) realloc(ctx->names, sizeof(char *) * (ctx->nmechs + 1)); + ctx->names[ctx->nmechs] = strdup(mech->name); + + ctx->nmechs++; + + log_debug(ZONE, "mech '%s' initialised", mech->name); + } + + i++; + } + + if(ctx->nmechs == 0) { + free(ctx); + return NULL; + } + + return ctx; +} + +void scod_ctx_free(scod_ctx_t ctx) { + int i; + + assert((int) ctx); + + log_debug(ZONE, "freeing scod context"); + + for(i = 0; i < ctx->nmechs; i++) { + log_debug(ZONE, "freeing '%s' mech", ctx->mechs[i]->name); + + if(ctx->mechs[i]->free != NULL) + (ctx->mechs[i]->free)(ctx->mechs[i]); + + free(ctx->mechs[i]); + free(ctx->names[i]); + } + + free(ctx->mechs); + free(ctx->names); + free(ctx); +} + +int scod_mech_flags(scod_ctx_t ctx, char *name) { + int i; + + assert((int) ctx); + assert((int) name); + + for(i = 0; i < ctx->nmechs; i++) + if(strcmp(ctx->mechs[i]->name, name) == 0) + return ctx->mechs[i]->flags; + + return 0; +} + +scod_t scod_new(scod_ctx_t ctx, scod_type_t type) { + scod_t sd; + + assert((int) ctx); + assert((int) (type == sd_type_CLIENT || type == sd_type_SERVER)); + + log_debug(ZONE, "creating new scod"); + + sd = (scod_t) malloc(sizeof(struct _scod_st)); + memset(sd, 0, sizeof(struct _scod_st)); + + sd->ctx = ctx; + + sd->type = type; + + return sd; +} + +void scod_free(scod_t sd) { + assert((int) sd); + + log_debug(ZONE, "freeing scod"); + + if(sd->authzid != NULL) free(sd->authzid); + if(sd->authnid != NULL) free(sd->authnid); + if(sd->pass != NULL) free(sd->pass); + if(sd->realm != NULL) free(sd->realm); + + free(sd); +} + +static scod_mech_t _scod_get_mech(scod_ctx_t ctx, char *name) { + int i; + + log_debug(ZONE, "looking for mech '%s'", name); + + for(i = 0; i < ctx->nmechs; i++) + if(strcmp(ctx->mechs[i]->name, name) == 0) + return ctx->mechs[i]; + + return NULL; +} + +int scod_client_start(scod_t sd, char *name, char *authzid, char *authnid, char *pass, char **resp, int *resplen) { + int ret; + + assert((int) sd); + assert((int) name); + assert((int) authnid); + assert((int) pass); + assert((int) resp); + assert((int) resplen); + + *resp = NULL; + *resplen = 0; + + if(sd->type != sd_type_CLIENT) + return sd_err_WRONG_TYPE; + + if(sd->authd || sd->failed) + return sd_err_COMPLETED; + + log_debug(ZONE, "client start; authzid=%s, authnid=%s, pass=%s", authzid, authnid, pass); + + if(sd->mech != NULL) + return sd_err_IN_PROGRESS; + + if((sd->mech = _scod_get_mech(sd->ctx, name)) == NULL) + return sd_err_UNKNOWN_MECH; + + if(authzid != NULL) sd->authzid = strdup(authzid); + sd->authnid = strdup(authnid); + sd->pass = strdup(pass); + + if(sd->mech->client_start != NULL) + ret = (sd->mech->client_start)(sd->mech, sd, resp, resplen); + else + ret = sd_err_NOT_IMPLEMENTED; + + if(ret == sd_SUCCESS) + sd->authd = 1; + else if((ret & sd_auth_MASK) == sd_auth_MASK) + sd->failed = 1; + + return ret; +} + +int scod_client_step(scod_t sd, const char *chal, int challen, char **resp, int *resplen) { + int ret; + + assert((int) sd); + assert((int) chal); + assert((int) challen); + assert((int) resp); + assert((int) resplen); + + *resp = NULL; + *resplen = 0; + + if(sd->type != sd_type_CLIENT) + return sd_err_WRONG_TYPE; + + if(sd->authd || sd->failed) + return sd_err_COMPLETED; + + log_debug(ZONE, "client step"); + + if(sd->mech->client_step != NULL) + ret = (sd->mech->client_step)(sd->mech, sd, chal, challen, resp, resplen); + else + ret = sd_err_NOT_IMPLEMENTED; + + if(ret == sd_SUCCESS) + sd->authd = 1; + else if((ret & sd_auth_MASK) == sd_auth_MASK) + sd->failed = 1; + + return ret; +} + +int scod_server_start(scod_t sd, char *name, char *realm, const char *resp, int resplen, char **chal, int *challen) { + int ret; + + assert((int) sd); + assert((int) name); + assert((int) resp); + assert((int) chal); + assert((int) challen); + + *chal = NULL; + *challen = 0; + + if(sd->type != sd_type_SERVER) + return sd_err_WRONG_TYPE; + + if(sd->authd || sd->failed) + return sd_err_COMPLETED; + + log_debug(ZONE, "server start"); + + if(sd->mech != NULL) + return sd_err_IN_PROGRESS; + + if((sd->mech = _scod_get_mech(sd->ctx, name)) == NULL) + return sd_err_UNKNOWN_MECH; + + if(realm != NULL) + sd->realm = strdup(realm); + + if(sd->mech->server_start != NULL) + ret = (sd->mech->server_start)(sd->mech, sd, resp, resplen, chal, challen); + else + ret = sd_err_NOT_IMPLEMENTED; + + if(ret == sd_SUCCESS) + sd->authd = 1; + else if((ret & sd_auth_MASK) == sd_auth_MASK) + sd->failed = 1; + + return ret; +} + +int scod_server_step(scod_t sd, const char *resp, int resplen, char **chal, int *challen) { + int ret; + + assert((int) sd); + assert((int) resp); + assert((int) chal); + assert((int) challen); + + *chal = NULL; + *challen = 0; + + if(sd->type != sd_type_SERVER) + return sd_err_WRONG_TYPE; + + if(sd->authd || sd->failed) + return sd_err_COMPLETED; + + log_debug(ZONE, "server step"); + + if(sd->mech->client_start != NULL) + ret = (sd->mech->server_step)(sd->mech, sd, resp, resplen, chal, challen); + else + ret = sd_err_NOT_IMPLEMENTED; + + if(ret == sd_SUCCESS) + sd->authd = 1; + else if((ret & sd_auth_MASK) == sd_auth_MASK) + sd->failed = 1; + + return ret; +} + +int scod_sasl_encode(scod_t sd, const char *in, int inlen, char **out, char *outlen) { + assert((int) sd); + assert((int) in); + assert((int) out); + assert((int) outlen); + + log_debug(ZONE, "encode"); + + if(sd->mech->encode != NULL) + return (sd->mech->encode)(sd->mech, sd, in, inlen, out, outlen); + + *out = (char *) malloc(sizeof(char) * inlen); + memcpy(*out, in, inlen); + *outlen = inlen; + + return sd_SUCCESS; +} + +int scod_sasl_decode(scod_t sd, const char *in, int inlen, char **out, char *outlen) { + assert((int) sd); + assert((int) in); + assert((int) out); + assert((int) outlen); + + log_debug(ZONE, "decode"); + + if(sd->mech->decode != NULL) + return (sd->mech->decode)(sd->mech, sd, in, inlen, out, outlen); + + *out = (char *) malloc(sizeof(char) * inlen); + memcpy(*out, in, inlen); + *outlen = inlen; + + return sd_SUCCESS; +} diff --git a/scod/scod.h b/scod/scod.h new file mode 100644 index 00000000..ab8ccf9a --- /dev/null +++ b/scod/scod.h @@ -0,0 +1,144 @@ +/* + * scod - a minimal sasl implementation for jabberd2 + * Copyright (c) 2003 Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifndef INCL_SCOD_H +#define INCL_SCOD_H + +#include "util/util.h" + +typedef struct _scod_ctx_st *scod_ctx_t; +typedef struct _scod_st *scod_t; +typedef struct _scod_mech_st *scod_mech_t; + +#define sd_cb_GET_PASS (0x00) +#define sd_cb_CHECK_PASS (0x01) +#define sd_cb_CHECK_AUTHZID (0x02) +#define sd_cb_DIGEST_MD5_CHOOSE_REALM (0x03) +#define sd_cb_ANONYMOUS_GEN_AUTHZID (0x04) + +typedef int (*scod_callback_t)(scod_t sd, int cb, void *arg, void **res, void *cbarg); + +typedef struct _scod_cb_creds_st { + char *authnid; + char *realm; + char *pass; + char *authzid; +} *scod_cb_creds_t; + +struct _scod_ctx_st { + scod_callback_t cb; + void *cbarg; + + scod_mech_t *mechs; + int nmechs; + + char **names; +}; + +typedef enum { + sd_type_NONE = 0, + sd_type_CLIENT = 1, + sd_type_SERVER = 2 +} scod_type_t; + +struct _scod_st { + scod_ctx_t ctx; + + scod_type_t type; + + scod_mech_t mech; + + void *mech_data; + + char *authzid; + char *authnid; + char *pass; + + char *realm; + + int authd; + int failed; + + void *app_private; +}; + +#define sd_flag_CHECK_PASS (0x01) +#define sd_flag_GET_PASS (0x02) + +struct _scod_mech_st { + scod_ctx_t ctx; + + void *private; + + char *name; + + int flags; + + int (*client_start)(scod_mech_t mech, scod_t sd, char **resp, int *resplen); + int (*client_step)(scod_mech_t mech, scod_t sd, const char *chal, int challen, char **resp, int *resplen); + + int (*server_start)(scod_mech_t mech, scod_t sd, const char *resp, int resplen, char **chal, int *challen); + int (*server_step)(scod_mech_t mech, scod_t sd, const char *resp, int resplen, char **chal, int *challen); + + int (*encode)(scod_mech_t mech, scod_t sd, const char *in, int inlen, char **out, char *outlen); + int (*decode)(scod_mech_t mech, scod_t sd, const char *in, int inlen, char **out, char *outlen); + + void (*free)(scod_mech_t mech); +}; + +typedef int (*scod_mech_init_fn)(scod_mech_t); + +scod_ctx_t scod_ctx_new(scod_callback_t cb, void *cbarg); +void scod_ctx_free(scod_ctx_t ctx); + +int scod_mech_flags(scod_ctx_t ctx, char *name); + +scod_t scod_new(scod_ctx_t ctx, scod_type_t type); +void scod_free(scod_t sd); + +int scod_client_start(scod_t sd, char *name, char *authzid, char *authnid, char *pass, char **resp, int *resplen); +int scod_client_step(scod_t sd, const char *chal, int challen, char **resp, int *resplen); + +int scod_server_start(scod_t sd, char *name, char *realm, const char *resp, int resplen, char **chal, int *challen); +int scod_server_step(scod_t sd, const char *resp, int resplen, char **chal, int *challen); + +int scod_sasl_encode(scod_t sd, const char *in, int inlen, char **out, char *outlen); +int scod_sasl_decode(scod_t sd, const char *in, int inlen, char **out, char *outlen); + +#define sd_SUCCESS (0x00) +#define sd_CONTINUE (0x01) + +#define sd_err_NOT_IMPLEMENTED (0x10) +#define sd_err_IN_PROGRESS (0x11) +#define sd_err_WRONG_TYPE (0x12) +#define sd_err_UNKNOWN_MECH (0x13) +#define sd_err_COMPLETED (0x14) +#define sd_err_OPTS_REQUIRED (0x15) +#define sd_err_MASK (0x10) + +#define sd_auth_USER_UNKNOWN (0x20) +#define sd_auth_AUTH_FAILED (0x21) +#define sd_auth_MALFORMED_DATA (0x22) +#define sd_auth_AUTHZID_REQUIRED (0x23) +#define sd_auth_MISMATCH (0x24) +#define sd_auth_NOT_OFFERED (0x25) +#define sd_auth_AUTHZID_POLICY (0x26) +#define sd_auth_MASK (0x20) + +#endif diff --git a/sm/.cvsignore b/sm/.cvsignore new file mode 100644 index 00000000..7ad73325 --- /dev/null +++ b/sm/.cvsignore @@ -0,0 +1,5 @@ +sm +Makefile +Makefile.in +.deps +.libs diff --git a/sm/Makefile.am b/sm/Makefile.am new file mode 100644 index 00000000..df463f1a --- /dev/null +++ b/sm/Makefile.am @@ -0,0 +1,110 @@ +INCLUDES = -DCONFIG_DIR=\"$(sysconfdir)\" + +bin_PROGRAMS = sm +lib_LTLIBRARIES = libmod_active.la \ + libmod_announce.la \ + libmod_deliver.la \ + libmod_disco.la \ + libmod_disco-publish.la \ + libmod_echo.la \ + libmod_help.la \ + libmod_iq-last.la \ + libmod_iq-private.la \ + libmod_iq-time.la \ + libmod_iq-vcard.la \ + libmod_iq-version.la \ + libmod_offline.la \ + libmod_presence.la \ + libmod_privacy.la \ + libmod_roster.la \ + libmod_session.la \ + libmod_template-roster.la \ + libmod_vacation.la \ + libmod_validate.la + +noinst_HEADERS = sm.h +sm_SOURCES = aci.c \ + dispatch.c \ + feature.c \ + main.c \ + mm.c \ + object.c \ + pkt.c \ + pres.c \ + sess.c \ + sm.c \ + user.c \ + storage.c \ + storage_db.c \ + storage_fs.c \ + storage_mysql.c \ + storage_pgsql.c \ + storage_oracle.c \ + storage_sqlite.c + +sm_LDFLAGS = -Wl,-export-dynamic +sm_LDADD = $(top_builddir)/sx/libsx.la \ + $(top_builddir)/mio/libmio.la \ + $(top_builddir)/util/libutil.la \ + $(top_builddir)/subst/libsubst.la \ + $(top_builddir)/expat/libexpat.la + +libmod_active_la_SOURCES = mod_active.c +libmod_active_la_LDFLAGS = -shared -E + +libmod_announce_la_SOURCES = mod_announce.c +libmod_announce_la_LDFLAGS = -shared -E + +libmod_deliver_la_SOURCES = mod_deliver.c +libmod_deliver_la_LDFLAGS = -shared -E + +libmod_disco_la_SOURCES = mod_disco.c +libmod_disco_la_LDFLAGS = -shared -E + +libmod_disco_publish_la_SOURCES = mod_disco_publish.c +libmod_disco_publish_la_LDFLAGS = -shared -E + +libmod_echo_la_SOURCES = mod_echo.c +libmod_echo_la_LDFLAGS = -shared -E + +libmod_help_la_SOURCES = mod_help.c +libmod_help_la_LDFLAGS = -shared -E + +libmod_iq_last_la_SOURCES = mod_iq_last.c +libmod_iq_last_la_LDFLAGS = -shared -E + +libmod_iq_private_la_SOURCES = mod_iq_private.c +libmod_iq_private_la_LDFLAGS = -shared -E + +libmod_iq_time_la_SOURCES = mod_iq_time.c +libmod_iq_time_la_LDFLAGS = -shared -E + +libmod_iq_vcard_la_SOURCES = mod_iq_vcard.c +libmod_iq_vcard_la_LDFLAGS = -shared -E + +libmod_iq_version_la_SOURCES = mod_iq_version.c +libmod_iq_version_la_LDFLAGS = -shared -E + +libmod_offline_la_SOURCES = mod_offline.c +libmod_offline_la_LDFLAGS = -shared -E + +libmod_presence_la_SOURCES = mod_presence.c +libmod_presence_la_LDFLAGS = -shared -E + +libmod_privacy_la_SOURCES = mod_privacy.c +libmod_privacy_la_LDFLAGS = -shared -E + +libmod_roster_la_SOURCES = mod_roster.c +libmod_roster_la_LDFLAGS = -shared -E + +libmod_session_la_SOURCES = mod_session.c +libmod_session_la_LDFLAGS = -shared -E + +libmod_template_roster_la_SOURCES = mod_template_roster.c +libmod_template_roster_la_LDFLAGS = -shared -E + +libmod_vacation_la_SOURCES = mod_vacation.c +libmod_vacation_la_LDFLAGS = -shared -E + +libmod_validate_la_SOURCES = mod_validate.c +libmod_validate_la_LDFLAGS = -shared -E diff --git a/sm/aci.c b/sm/aci.c new file mode 100644 index 00000000..dd58dc60 --- /dev/null +++ b/sm/aci.c @@ -0,0 +1,146 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/aci.c + * @brief access control manager + * @author Robert Norris + * $Date: 2005/09/10 10:23:50 $ + * $Revision: 1.16 $ + */ + +xht aci_load(sm_t sm) +{ + xht acls; + int aelem, jelem, attr; + char type[33]; + jid_t list, jid; + + log_debug(ZONE, "loading aci"); + + acls = xhash_new(51); + + if((aelem = nad_find_elem(sm->config->nad, 0, -1, "aci", 1)) < 0) + return acls; + + aelem = nad_find_elem(sm->config->nad, aelem, -1, "acl", 1); + while(aelem >= 0) + { + list = NULL; + + if((attr = nad_find_attr(sm->config->nad, aelem, -1, "type", NULL)) < 0) + { + aelem = nad_find_elem(sm->config->nad, aelem, -1, "acl", 0); + continue; + } + + snprintf(type, 33, "%.*s", NAD_AVAL_L(sm->config->nad, attr), NAD_AVAL(sm->config->nad, attr)); + + log_debug(ZONE, "building list for '%s'", type); + + jelem = nad_find_elem(sm->config->nad, aelem, -1, "jid", 1); + while(jelem >= 0) + { + if(NAD_CDATA_L(sm->config->nad, jelem) > 0) + { + jid = jid_new(sm->pc, NAD_CDATA(sm->config->nad, jelem), NAD_CDATA_L(sm->config->nad, jelem)); + list = jid_append(list, jid); + + log_debug(ZONE, "added '%s'", jid_user(jid)); + + jid_free(jid); + } + + jelem = nad_find_elem(sm->config->nad, jelem, -1, "jid", 0); + } + + if(list != NULL) { + xhash_put(acls, pstrdup(xhash_pool(acls), type), (void *) list); + } + + aelem = nad_find_elem(sm->config->nad, aelem, -1, "acl", 0); + } + + return acls; +} + +/** see if a jid is in an acl */ +int aci_check(xht acls, char *type, jid_t jid) +{ + jid_t list, dup; + + dup = jid_dup(jid); + if (dup->resource[0]) { + /* resourceless version */ + dup->resource[0] = '\0'; + dup->dirty = 1; + } + + log_debug(ZONE, "checking for '%s' in acl 'all'", jid_full(jid)); + list = (jid_t) xhash_get(acls, "all"); + if(jid_search(list, jid)) { + jid_free(dup); + return 1; + } + + log_debug(ZONE, "checking for '%s' in acl 'all'", jid_user(dup)); + if(jid_search(list, dup)) { + jid_free(dup); + return 1; + } + + if(type != NULL) { + log_debug(ZONE, "checking for '%s' in acl '%s'", jid_full(jid), type); + list = (jid_t) xhash_get(acls, type); + if(jid_search(list, jid)) { + jid_free(dup); + return 1; + } + + log_debug(ZONE, "checking for '%s' in acl '%s'", jid_user(dup), type); + if(jid_search(list, dup)) { + jid_free(dup); + return 1; + } + } + + jid_free(dup); + return 0; +} + +void aci_unload(xht acls) +{ + jid_t list, jid; + + log_debug(ZONE, "unloading acls"); + + if(xhash_iter_first(acls)) + do { + xhash_iter_get(acls, NULL, (void *) &list); + while (list != NULL) { + jid = list; + list = list->next; + jid_free(jid); + } + } while(xhash_iter_next(acls)); + + return; +} diff --git a/sm/dispatch.c b/sm/dispatch.c new file mode 100644 index 00000000..7979876d --- /dev/null +++ b/sm/dispatch.c @@ -0,0 +1,143 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/dispatch.c + * @brief packet dispatcher + * @author Robert Norris + * $Date: 2005/09/09 05:31:04 $ + * $Revision: 1.18 $ + */ + +/** main packet dispatcher */ +void dispatch(sm_t sm, pkt_t pkt) { + user_t user; + mod_ret_t ret; + + /* handle broadcasts */ + if(pkt->rtype == route_BROADCAST) { + log_debug(ZONE, "can't handle broadcast routes (yet), dropping"); + pkt_free(pkt); + return; + } + + /* routing errors, add a im error */ + if(pkt->rtype & route_ERROR) + pkt_error(pkt, stanza_err_REMOTE_SERVER_NOT_FOUND); + + /* + * - if its from the router (non-route) it goes straight to pkt_router + * - hand it to in_router chain + * - if its for the sm itself (no user), hand it to the pkt_sm chain + * - find the user + * - hand to pkt_user + */ + + /* non route packets are special-purpose things from the router */ + if(!(pkt->rtype & route_UNICAST)) { + ret = mm_pkt_router(pkt->sm->mm, pkt); + switch(ret) { + case mod_HANDLED: + break; + + case mod_PASS: + default: + /* don't ever bounce these */ + pkt_free(pkt); + + break; + } + + return; + } + + /* preprocessing */ + ret = mm_in_router(pkt->sm->mm, pkt); + switch(ret) { + case mod_HANDLED: + return; + + case mod_PASS: + break; + + default: + pkt_router(pkt_error(pkt, -ret)); + return; + } + + /* has to come from someone */ + if(pkt->from == NULL) { + pkt_router(pkt_error(pkt, stanza_err_BAD_REQUEST)); + return; + } + + /* packet is for the sm itself */ + if(pkt->to != NULL && *pkt->to->node == '\0') { + ret = mm_pkt_sm(pkt->sm->mm, pkt); + switch(ret) { + case mod_HANDLED: + break; + + case mod_PASS: + /* ignore IQ result packets that haven't been handled - XMPP 9.2.3.4 */ + if(pkt->type == pkt_IQ_RESULT) + break; + else + ret = -stanza_err_FEATURE_NOT_IMPLEMENTED; + + default: + pkt_router(pkt_error(pkt, -ret)); + + break; + } + + return; + } + + /* get the user */ + user = user_load(sm, pkt->to); + if(user == NULL) { + pkt_router(pkt_error(pkt, stanza_err_ITEM_NOT_FOUND)); + return; + } + + ret = mm_pkt_user(pkt->sm->mm, user, pkt); + switch(ret) { + case mod_HANDLED: + break; + + case mod_PASS: + /* ignore IQ result packets that haven't been handled - XMPP 9.2.3.4 */ + if(pkt->type == pkt_IQ_RESULT) + break; + else + ret = -stanza_err_FEATURE_NOT_IMPLEMENTED; + + default: + pkt_router(pkt_error(pkt, -ret)); + + break; + } + + /* if they have no sessions, they were only loaded to do delivery, so free them */ + if(user->sessions == NULL) + user_free(user); +} diff --git a/sm/feature.c b/sm/feature.c new file mode 100644 index 00000000..8a088a6d --- /dev/null +++ b/sm/feature.c @@ -0,0 +1,56 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/feature.c + * @brief feature registration + * @author Robert Norris + * $Date: 2005/08/17 11:10:12 $ + * $Revision: 1.9 $ + */ + +/* + * these are simple wrappers around xhash for the moment. perhaps a little + * redundant, but they will give a good abstraction, and make it easier to + * add stuff in the future .. f-neg comes to mind. + */ + +/** register a feature */ +void feature_register(sm_t sm, char *feature) +{ + log_debug(ZONE, "registering feature %s", feature); + + xhash_put(sm->features, pstrdup(xhash_pool(sm->features), feature), (void *) ((int) xhash_get(sm->features, feature) + 1)); +} + +/** unregister feature */ +void feature_unregister(sm_t sm, char *feature) +{ + int refcount = (int) xhash_get(sm->features, feature); + + log_debug(ZONE, "unregistering feature %s", feature); + + if (refcount == 1) { + xhash_zap(sm->features, feature); + } else if (refcount > 1) { + xhash_put(sm->features, feature, (void *) ((int) refcount - 1)); + } +} diff --git a/sm/main.c b/sm/main.c new file mode 100644 index 00000000..e3197d0d --- /dev/null +++ b/sm/main.c @@ -0,0 +1,421 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +#ifdef HAVE_IDN + #include +#endif + +/** @file sm/main.c + * @brief initialisation + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.57 $ + */ + +static sig_atomic_t sm_shutdown = 0; +static sig_atomic_t sm_logrotate = 0; +static sm_t sm = NULL; +static char* config_file; + +static void _sm_signal(int signum) +{ + sm_shutdown = 1; + sm_lost_router = 0; +} + +static void _sm_signal_hup(int signum) +{ + log_write(sm->log, LOG_NOTICE, "HUP handled. reloading modules..."); + + sm_logrotate = 1; + + /* reload dynamic modules */ + config_t conf; + conf = config_new(); + if (conf && config_load(conf, config_file) == 0) { + config_free(sm->config); + sm->config = conf; + /*_sm_config_expand(sm);*/ /* we want to reload modules only */ + } else { + log_write(sm->log, LOG_WARNING, "couldn't reload config (%s)", config_file); + if (conf) config_free(conf); + } + mm_free(sm->mm); + sm->mm = mm_new(sm); +} + +/** store the process id */ +static void _sm_pidfile(sm_t sm) { + char *pidfile; + FILE *f; + pid_t pid; + + pidfile = config_get_one(sm->config, "pidfile", 0); + if(pidfile == NULL) + return; + + pid = getpid(); + + if((f = fopen(pidfile, "w+")) == NULL) { + log_write(sm->log, LOG_ERR, "couldn't open %s for writing: %s", pidfile, strerror(errno)); + return; + } + + if(fprintf(f, "%d", pid) < 0) { + log_write(sm->log, LOG_ERR, "couldn't write to %s: %s", pidfile, strerror(errno)); + return; + } + + fclose(f); + + log_write(sm->log, LOG_INFO, "process id is %d, written to %s", pid, pidfile); +} + +/** pull values out of the config file */ +static void _sm_config_expand(sm_t sm) +{ + char *str; + + sm->id = config_get_one(sm->config, "id", 0); + if(sm->id == NULL) + sm->id = "localhost"; + + sm->router_ip = config_get_one(sm->config, "router.ip", 0); + if(sm->router_ip == NULL) + sm->router_ip = "127.0.0.1"; + + sm->router_port = j_atoi(config_get_one(sm->config, "router.port", 0), 5347); + + sm->router_user = config_get_one(sm->config, "router.user", 0); + if(sm->router_user == NULL) + sm->router_user = "jabberd"; + sm->router_pass = config_get_one(sm->config, "router.pass", 0); + if(sm->router_pass == NULL) + sm->router_pass = "secret"; + + sm->router_pemfile = config_get_one(sm->config, "router.pemfile", 0); + + sm->retry_init = j_atoi(config_get_one(sm->config, "router.retry.init", 0), 3); + sm->retry_lost = j_atoi(config_get_one(sm->config, "router.retry.lost", 0), 3); + if((sm->retry_sleep = j_atoi(config_get_one(sm->config, "router.retry.sleep", 0), 2)) < 1) + sm->retry_sleep = 1; + + sm->log_type = log_STDOUT; + if(config_get(sm->config, "log") != NULL) { + if((str = config_get_attr(sm->config, "log", 0, "type")) != NULL) { + if(strcmp(str, "file") == 0) + sm->log_type = log_FILE; + else if(strcmp(str, "syslog") == 0) + sm->log_type = log_SYSLOG; + } + } + + if(sm->log_type == log_SYSLOG) { + sm->log_facility = config_get_one(sm->config, "log.facility", 0); + sm->log_ident = config_get_one(sm->config, "log.ident", 0); + if(sm->log_ident == NULL) + sm->log_ident = "jabberd/sm"; + } else if(sm->log_type == log_FILE) + sm->log_ident = config_get_one(sm->config, "log.file", 0); +} + +static int _sm_router_connect(sm_t sm) { + log_write(sm->log, LOG_NOTICE, "attempting connection to router at %s, port=%d", sm->router_ip, sm->router_port); + + sm->fd = mio_connect(sm->mio, sm->router_port, sm->router_ip, sm_mio_callback, (void *) sm); + if(sm->fd < 0) { + if(errno == ECONNREFUSED) + sm_lost_router = 1; + log_write(sm->log, LOG_NOTICE, "connection attempt to router failed: %s (%d)", strerror(errno), errno); + return 1; + } + + sm->router = sx_new(sm->sx_env, sm->fd, sm_sx_callback, (void *) sm); + sx_client_init(sm->router, 0, NULL, NULL, NULL, "1.0"); + + return 0; +} + +int main(int argc, char **argv) { + int optchar; + sess_t sess; + char id[1024]; +#ifdef POOL_DEBUG + time_t pool_time = 0; +#endif + +#ifdef HAVE_UMASK + umask((mode_t) 0027); +#endif + + srand(time(NULL)); + +#ifdef HAVE_WINSOCK2_H +/* get winsock running */ + { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + /* !!! tell user that we couldn't find a usable winsock dll */ + return 0; + } + } +#endif + + jabber_signal(SIGINT, _sm_signal); + jabber_signal(SIGTERM, _sm_signal); +#ifdef SIGHUP + jabber_signal(SIGHUP, _sm_signal_hup); +#endif +#ifdef SIGPIPE + jabber_signal(SIGPIPE, SIG_IGN); +#endif + + sm = (sm_t) malloc(sizeof(struct sm_st)); + memset(sm, 0, sizeof(struct sm_st)); + + /* load our config */ + sm->config = config_new(); + + config_file = CONFIG_DIR "/sm.xml"; + + /* cmdline parsing */ + while((optchar = getopt(argc, argv, "Dc:h?")) >= 0) + { + switch(optchar) + { + case 'c': + config_file = optarg; + break; + case 'D': +#ifdef DEBUG + set_debug_flag(1); +#else + printf("WARN: Debugging not enabled. Ignoring -D.\n"); +#endif + break; + case 'h': case '?': default: + fputs( + "sm - jabberd session manager (" VERSION ")\n" + "Usage: sm \n" + "Options are:\n" + " -c config file to use [default: " CONFIG_DIR "/sm.xml]\n" +#ifdef DEBUG + " -D Show debug output\n" +#endif + , + stdout); + config_free(sm->config); + free(sm); + return 1; + } + } + + if(config_load(sm->config, config_file) != 0) + { + fputs("sm: couldn't load config, aborting\n", stderr); + config_free(sm->config); + free(sm); + return 2; + } + + _sm_config_expand(sm); + + sm->log = log_new(sm->log_type, sm->log_ident, sm->log_facility); + log_write(sm->log, LOG_NOTICE, "starting up"); + + /* stringprep id (domain name) so that it's in canonical form */ + strncpy(id, sm->id, 1024); + id[sizeof(id)-1] = '\0'; +#ifdef HAVE_IDN + if (stringprep_nameprep(id, 1024) != 0) { + log_write(sm->log, LOG_ERR, "cannot stringprep id %s, aborting", sm->id); + exit(1); + } +#endif + sm->id = id; + + log_write(sm->log, LOG_NOTICE, "id: %s", sm->id); + + _sm_pidfile(sm); + + sm_signature(sm, "jabberd sm " VERSION); + + sm->pc = prep_cache_new(); + + /* start storage */ + sm->st = storage_new(sm); + if (sm->st == NULL) { + log_write(sm->log, LOG_ERR, "failed to initialise one or more storage drivers, aborting"); + exit(1); + } + + /* pre-index known namespaces */ + sm->xmlns = xhash_new(101); + xhash_put(sm->xmlns, uri_AUTH, (void *) ns_AUTH); + xhash_put(sm->xmlns, uri_REGISTER, (void *) ns_REGISTER); + xhash_put(sm->xmlns, uri_ROSTER, (void *) ns_ROSTER); + xhash_put(sm->xmlns, uri_AGENTS, (void *) ns_AGENTS); + xhash_put(sm->xmlns, uri_DELAY, (void *) ns_DELAY); + xhash_put(sm->xmlns, uri_BROWSE, (void *) ns_BROWSE); + xhash_put(sm->xmlns, uri_EVENT, (void *) ns_EVENT); + xhash_put(sm->xmlns, uri_GATEWAY, (void *) ns_GATEWAY); + xhash_put(sm->xmlns, uri_EXPIRE, (void *) ns_EXPIRE); + xhash_put(sm->xmlns, uri_SEARCH, (void *) ns_SEARCH); + xhash_put(sm->xmlns, uri_DISCO, (void *) ns_DISCO); + xhash_put(sm->xmlns, uri_DISCO_ITEMS, (void *) ns_DISCO_ITEMS); + xhash_put(sm->xmlns, uri_DISCO_INFO, (void *) ns_DISCO_INFO); + sm->xmlns_refcount = xhash_new(101); + + /* supported features */ + sm->features = xhash_new(101); + + /* load acls */ + sm->acls = aci_load(sm); + + /* the core supports iq, everything else is handled by the modules */ + feature_register(sm, "iq"); + + /* startup the modules */ + sm->mm = mm_new(sm); + + log_write(sm->log, LOG_NOTICE, "version: %s", sm->signature); + + sm->sessions = xhash_new(401); + + sm->users = xhash_new(401); + + sm->sx_env = sx_env_new(); + +#ifdef HAVE_SSL + if(sm->router_pemfile != NULL) { + sm->sx_ssl = sx_env_plugin(sm->sx_env, sx_ssl_init, sm->router_pemfile, NULL); + if(sm->sx_ssl == NULL) { + log_write(sm->log, LOG_ERR, "failed to load SSL pemfile, SSL disabled"); + sm->router_pemfile = NULL; + } + } +#endif + + /* get sasl online */ + sm->sx_sasl = sx_env_plugin(sm->sx_env, sx_sasl_init, "xmpp", SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT, NULL, NULL, 0); + if(sm->sx_sasl == NULL) { + log_write(sm->log, LOG_ERR, "failed to initialise SASL context, aborting"); + exit(1); + } + + sm->mio = mio_new(1024); + + sm->retry_left = sm->retry_init; + _sm_router_connect(sm); + + while(!sm_shutdown) { + mio_run(sm->mio, 5); + + if(sm_logrotate) { + log_write(sm->log, LOG_NOTICE, "reopening log ..."); + log_free(sm->log); + sm->log = log_new(sm->log_type, sm->log_ident, sm->log_facility); + log_write(sm->log, LOG_NOTICE, "log started"); + + sm_logrotate = 0; + } + + if(sm_lost_router) { + if(sm->retry_left < 0) { + log_write(sm->log, LOG_NOTICE, "attempting reconnect"); + sleep(sm->retry_sleep); + sm_lost_router = 0; + _sm_router_connect(sm); + } + + else if(sm->retry_left == 0) { + sm_shutdown = 1; + } + + else { + log_write(sm->log, LOG_NOTICE, "attempting reconnect (%d left)", sm->retry_left); + sm->retry_left--; + sleep(sm->retry_sleep); + sm_lost_router = 0; + _sm_router_connect(sm); + } + } + +#ifdef POOL_DEBUG + if(time(NULL) > pool_time + 60) { + pool_stat(1); + pool_time = time(NULL); + } +#endif + } + + log_write(sm->log, LOG_NOTICE, "shutting down"); + + /* shut down sessions */ + if(xhash_iter_first(sm->sessions)) + do { + xhash_iter_get(sm->sessions, NULL, (void *) &sess); + sm_c2s_action(sess, "ended", NULL); + sess_end(sess); + } while(xhash_count(sm->sessions) > 0); + + xhash_free(sm->sessions); + + mio_free(sm->mio); + + mm_free(sm->mm); + storage_free(sm->st); + + aci_unload(sm->acls); + xhash_free(sm->acls); + xhash_free(sm->features); + xhash_free(sm->xmlns); + xhash_free(sm->users); + + sx_free(sm->router); + + sx_env_free(sm->sx_env); + + prep_cache_free(sm->pc); + + log_free(sm->log); + + config_free(sm->config); + + free(sm); + +#ifdef POOL_DEBUG + pool_stat(1); +#endif + +#ifdef HAVE_WINSOCK2_H + WSACleanup(); +#endif + + return 0; +} diff --git a/sm/mm.c b/sm/mm.c new file mode 100644 index 00000000..28b07c2d --- /dev/null +++ b/sm/mm.c @@ -0,0 +1,680 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" +#ifdef WIN32 + #include +#else + #include +#endif + +/** @file sm/mm.c + * @brief module manager + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.40 $ + */ + +/* these functions implement a multiplexor to get calls to the correct module + * for the given type */ + +/* Notes on dynamic modules (cedricv@) : + Modules are searched by name mod_[modulename].so or mod_[modulename].dll + depending platform. + You have to set [full_path] within in sm.xml config, + else it will only search in LD_LIBRARY_PATH or c:\windows\system32 + */ + +mm_t mm_new(sm_t sm) { + mm_t mm; + int celem, melem, attr, i, *nlist = NULL; + char id[13], name[32], mod_fullpath[512], arg[1024], *modules_path; + mod_chain_t chain = (mod_chain_t) NULL; + mod_instance_t **list = NULL, mi; + module_t mod; + + mm = (mm_t) malloc(sizeof(struct mm_st)); + memset(mm, 0, sizeof(struct mm_st)); + + mm->sm = sm; + mm->modules = xhash_new(101); + + if((celem = nad_find_elem(sm->config->nad, 0, -1, "modules", 1)) < 0) + return mm; + + modules_path = config_get_one(sm->config, "modules.path", 0); + if (modules_path != NULL) + log_write(sm->log, LOG_NOTICE, "modules search path: %s", modules_path); + else + log_write(sm->log, LOG_WARNING, "modules search path undefined !"); + + celem = nad_find_elem(sm->config->nad, celem, -1, "chain", 1); + while(celem >= 0) { + if((attr = nad_find_attr(sm->config->nad, celem, -1, "id", NULL)) < 0) { + celem = nad_find_elem(sm->config->nad, celem, -1, "chain", 0); + continue; + } + + snprintf(id, 13, "%.*s", NAD_AVAL_L(sm->config->nad, attr), NAD_AVAL(sm->config->nad, attr)); + id[12] = '\0'; + + log_debug(ZONE, "processing config for chain '%s'", id); + + list = NULL; + if(strcmp(id, "sess-start") == 0) { + chain = chain_SESS_START; + list = &mm->sess_start; + nlist = &mm->nsess_start; + } + else if(strcmp(id, "sess-end") == 0) { + chain = chain_SESS_END; + list = &mm->sess_end; + nlist = &mm->nsess_end; + } + else if(strcmp(id, "in-sess") == 0) { + chain = chain_IN_SESS; + list = &mm->in_sess; + nlist = &mm->nin_sess; + } + else if(strcmp(id, "in-router") == 0) { + chain = chain_IN_ROUTER; + list = &mm->in_router; + nlist = &mm->nin_router; + } + else if(strcmp(id, "out-sess") == 0) { + chain = chain_OUT_SESS; + list = &mm->out_sess; + nlist = &mm->nout_sess; + } + else if(strcmp(id, "out-router") == 0) { + chain = chain_OUT_ROUTER; + list = &mm->out_router; + nlist = &mm->nout_router; + } + else if(strcmp(id, "pkt-sm") == 0) { + chain = chain_PKT_SM; + list = &mm->pkt_sm; + nlist = &mm->npkt_sm; + } + else if(strcmp(id, "pkt-user") == 0) { + chain = chain_PKT_USER; + list = &mm->pkt_user; + nlist = &mm->npkt_user; + } + else if(strcmp(id, "pkt-router") == 0) { + chain = chain_PKT_ROUTER; + list = &mm->pkt_router; + nlist = &mm->npkt_router; + } + else if(strcmp(id, "user-load") == 0) { + chain = chain_USER_LOAD; + list = &mm->user_load; + nlist = &mm->nuser_load; + } + else if(strcmp(id, "user-create") == 0) { + chain = chain_USER_CREATE; + list = &mm->user_create; + nlist = &mm->nuser_create; + } + else if(strcmp(id, "user-delete") == 0) { + chain = chain_USER_DELETE; + list = &mm->user_delete; + nlist = &mm->nuser_delete; + } + + if(list == NULL) { + log_write(sm->log, LOG_ERR, "unknown chain type '%s'", id); + + celem = nad_find_elem(sm->config->nad, celem, -1, "chain", 0); + continue; + } + + melem = nad_find_elem(sm->config->nad, celem, -1, "module", 1); + while(melem >= 0) { + if(NAD_CDATA_L(sm->config->nad, melem) <= 0) { + melem = nad_find_elem(sm->config->nad, melem, -1, "module", 0); + continue; + } + + arg[0] = '\0'; + attr = nad_find_attr(sm->config->nad, melem, -1, "arg", NULL); + if(attr >= 0) { + snprintf(arg, 1024, "%.*s", NAD_AVAL_L(sm->config->nad, attr), NAD_AVAL(sm->config->nad, attr)); + log_debug(ZONE, "module arg: %s", arg); + } + + snprintf(name, 32, "%.*s", NAD_CDATA_L(sm->config->nad, melem), NAD_CDATA(sm->config->nad, melem)); + + mod = xhash_get(mm->modules, name); + if(mod == NULL) { + mod = (module_t) malloc(sizeof(struct module_st)); + memset(mod, 0, sizeof(struct module_st)); + + mod->mm = mm; + mod->index = mm->nindex; + mod->name = strdup(name); + #ifndef WIN32 + if (modules_path != NULL) + snprintf(mod_fullpath, 512, "%slibmod_%s.so", modules_path, name); + else + snprintf(mod_fullpath, 512, "libmod_%s.so", name); + mod->handle = dlopen(mod_fullpath, RTLD_LAZY); + if (mod->handle != NULL) + mod->module_init_fn = dlsym(mod->handle, "module_init"); + #else + if (modules_path != NULL) + snprintf(mod_fullpath, 512, "%smod_%s.dll", modules_path, name); + else + snprintf(mod_fullpath, 512, "mod_%s.dll", name); + mod->handle = (void*) LoadLibrary(mod_fullpath); + if (mod->handle != NULL) + mod->module_init_fn = GetProcAddress((HMODULE) mod->handle, "module_init"); + #endif + + if (mod->handle != NULL && mod->module_init_fn != NULL) { + log_debug(ZONE, "preloaded module '%s' to chain '%s' (not added yet)", name, id); + xhash_put(mm->modules, mod->name, (void *) mod); + mm->nindex++; + } else { + #ifndef WIN32 + log_write(sm->log, LOG_ERR, "failed loading module '%s' to chain '%s' (%s)", name, id, dlerror()); + if (mod->handle != NULL) + dlclose(mod->handle); + #else + log_write(sm->log, LOG_ERR, "failed loading module '%s' to chain '%s' (errcode: %x)", name, id, GetLastError()); + if (mod->handle != NULL) + FreeLibrary((HMODULE) mod->handle); + #endif + + melem = nad_find_elem(sm->config->nad, melem, -1, "module", 0); + continue; + } + } + + mi = (mod_instance_t) malloc(sizeof(struct mod_instance_st)); + memset(mi, 0, sizeof(struct mod_instance_st)); + + mi->sm = sm; + mi->mod = mod; + mi->chain = chain; + mi->arg = (arg[0] == '\0') ? NULL : strdup(arg); + mi->seq = mod->init; + + if(mod->module_init_fn(mi) != 0) { + log_write(sm->log, LOG_ERR, "init for module '%s' (seq %d) failed", name, mi->seq); + free(mi); + + if(mod->init == 0) { + xhash_zap(mm->modules, mod->name); + + #ifndef WIN32 + if (mod->handle != NULL) + dlclose(mod->handle); + #else + if (mod->handle != NULL) + FreeLibrary((HMODULE) mod->handle); + #endif + + free(mod->name); + free(mod); + + mm->nindex--; + + melem = nad_find_elem(sm->config->nad, melem, -1, "module", 0); + continue; + } + } + + mod->init++; + + *list = (mod_instance_t *) realloc(*list, sizeof(mod_instance_t) * (*nlist + 1)); + (*list)[*nlist] = mi; + + log_write(sm->log, LOG_NOTICE, "module '%s' added to chain '%s' (order %d index %d seq %d)", mod->name, id, *nlist, mod->index, mi->seq); + + (*nlist)++; + + melem = nad_find_elem(sm->config->nad, melem, -1, "module", 0); + } + + celem = nad_find_elem(sm->config->nad, celem, -1, "chain", 0); + } + + return mm; +} + +static void _mm_reaper(xht modules, const char *module, void *val, void *arg) { + module_t mod = (module_t) val; + + if(mod->free != NULL) + (mod->free)(mod); + + #ifndef WIN32 + if (mod->handle != NULL) + dlclose(mod->handle); + #else + if (mod->handle != NULL) + FreeLibrary((HMODULE) mod->handle); + #endif + + free(mod->name); + free(mod); +} + +void mm_free(mm_t mm) { + int i, j, *nlist = NULL; + mod_instance_t **list = NULL, mi; + + /* close down modules */ + xhash_walk(mm->modules, _mm_reaper, NULL); + + /* free instances */ + for(i = 0; i < 12; i++) { + switch(i) { + case 0: + list = &mm->sess_start; + nlist = &mm->nsess_start; + break; + case 1: + list = &mm->sess_end; + nlist = &mm->nsess_end; + break; + case 2: + list = &mm->in_sess; + nlist = &mm->nin_sess; + break; + case 3: + list = &mm->in_router; + nlist = &mm->nin_router; + break; + case 4: + list = &mm->out_sess; + nlist = &mm->nout_sess; + break; + case 5: + list = &mm->out_router; + nlist = &mm->nout_router; + break; + case 6: + list = &mm->pkt_sm; + nlist = &mm->npkt_sm; + break; + case 7: + list = &mm->pkt_user; + nlist = &mm->npkt_user; + break; + case 8: + list = &mm->pkt_router; + nlist = &mm->npkt_router; + break; + case 9: + list = &mm->user_load; + nlist = &mm->nuser_load; + break; + case 10: + list = &mm->user_create; + nlist = &mm->nuser_create; + break; + case 11: + list = &mm->user_delete; + nlist = &mm->nuser_delete; + break; + } + + for(j = 0; j < *nlist; j++) { + mi = (*list)[j]; + if(mi->arg != NULL) + free(mi->arg); + free(mi); + } + } + + /* free lists */ + free(mm->sess_start); + free(mm->sess_end); + free(mm->in_sess); + free(mm->in_router); + free(mm->out_sess); + free(mm->out_router); + free(mm->pkt_sm); + free(mm->pkt_user); + free(mm->pkt_router); + free(mm->user_load); + free(mm->user_create); + free(mm->user_delete); + + xhash_free(mm->modules); + + free(mm); +} + +/** session starting */ +int mm_sess_start(mm_t mm, sess_t sess) { + int n, ret = 0; + mod_instance_t mi; + + log_debug(ZONE, "dispatching sess-start chain"); + + ret = 0; + for(n = 0; n < mm->nsess_start; n++) { + mi = mm->sess_start[n]; + if(mi == NULL || mi->mod->sess_start == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->sess_start)(mi, sess); + if(ret != 0) + break; + } + + log_debug(ZONE, "sess-start chain returning %d", ret); + + return ret; +} + +/** session ending */ +void mm_sess_end(mm_t mm, sess_t sess) { + int n; + mod_instance_t mi; + + log_debug(ZONE, "dispatching sess-end chain"); + + for(n = 0; n < mm->nsess_end; n++) { + mi = mm->sess_end[n]; + if(mi == NULL || mi->mod->sess_end == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + (mi->mod->sess_end)(mi, sess); + } + + log_debug(ZONE, "sess-end chain returning"); +} + +/** packets from active session */ +mod_ret_t mm_in_sess(mm_t mm, sess_t sess, pkt_t pkt) { + int n; + mod_instance_t mi; + mod_ret_t ret = mod_PASS; + + log_debug(ZONE, "dispatching in-sess chain"); + + ret = mod_PASS; + for(n = 0; n < mm->nin_sess; n++) { + mi = mm->in_sess[n]; + if(mi == NULL || mi->mod->in_sess == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->in_sess)(mi, sess, pkt); + if(ret != mod_PASS) + break; + } + + log_debug(ZONE, "in-sess chain returning %d", ret); + + return ret; +} + +/** packets from router */ +mod_ret_t mm_in_router(mm_t mm, pkt_t pkt) { + int n; + mod_instance_t mi; + mod_ret_t ret = mod_PASS; + + log_debug(ZONE, "dispatching in-router chain"); + + for(n = 0; n < mm->nin_router; n++) { + mi = mm->in_router[n]; + if(mi == NULL || mi->mod->in_router == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->in_router)(mi, pkt); + if(ret != mod_PASS) + break; + } + + log_debug(ZONE, "in-router chain returning %d", ret); + + return ret; +} + +/** packets to active session */ +mod_ret_t mm_out_sess(mm_t mm, sess_t sess, pkt_t pkt) { + int n; + mod_instance_t mi; + mod_ret_t ret = mod_PASS; + + log_debug(ZONE, "dispatching out-sess chain"); + + for(n = 0; n < mm->nout_sess; n++) { + mi = mm->out_sess[n]; + if(mi == NULL || mi->mod->out_sess == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->out_sess)(mi, sess, pkt); + if(ret != mod_PASS) + break; + } + + log_debug(ZONE, "out-sess chain returning %d", ret); + + return ret; +} + +/** packets to router */ +mod_ret_t mm_out_router(mm_t mm, pkt_t pkt) { + int n; + mod_instance_t mi; + mod_ret_t ret = mod_PASS; + + log_debug(ZONE, "dispatching out-router chain"); + + for(n = 0; n < mm->nout_router; n++) { + mi = mm->out_router[n]; + if(mi == NULL || mi->mod->out_router == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->out_router)(mi, pkt); + if(ret != mod_PASS) + break; + } + + log_debug(ZONE, "out-router chain returning %d", ret); + + return ret; +} + +/** packets for sm */ +mod_ret_t mm_pkt_sm(mm_t mm, pkt_t pkt) { + int n, ret = 0; + mod_instance_t mi; + + log_debug(ZONE, "dispatching pkt-sm chain"); + + for(n = 0; n < mm->npkt_sm; n++) { + mi = mm->pkt_sm[n]; + if(mi == NULL || mi->mod->pkt_sm == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->pkt_sm)(mi, pkt); + if(ret != mod_PASS) + break; + } + + log_debug(ZONE, "pkt-sm chain returning %d", ret); + + return ret; +} + +/** packets for user */ +mod_ret_t mm_pkt_user(mm_t mm, user_t user, pkt_t pkt) { + int n; + mod_instance_t mi; + mod_ret_t ret = mod_PASS; + + log_debug(ZONE, "dispatching pkt-user chain"); + + for(n = 0; n < mm->npkt_user; n++) { + mi = mm->pkt_user[n]; + if(mi == NULL || mi->mod->pkt_user == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->pkt_user)(mi, user, pkt); + if(ret != mod_PASS) + break; + } + + log_debug(ZONE, "pkt-user chain returning %d", ret); + + return ret; +} + +/** packets from the router */ +mod_ret_t mm_pkt_router(mm_t mm, pkt_t pkt) { + int n; + mod_instance_t mi; + mod_ret_t ret = mod_PASS; + + log_debug(ZONE, "dispatching pkt-router chain"); + + for(n = 0; n < mm->npkt_router; n++) { + mi = mm->pkt_router[n]; + if(mi == NULL || mi->mod->pkt_router == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->pkt_router)(mi, pkt); + if(ret != mod_PASS) + break; + } + + log_debug(ZONE, "pkt-router chain returning %d", ret); + + return ret; +} + +/** load user data */ +int mm_user_load(mm_t mm, user_t user) { + int n; + mod_instance_t mi; + int ret = 0; + + log_debug(ZONE, "dispatching user-load chain"); + + for(n = 0; n < mm->nuser_load; n++) { + mi = mm->user_load[n]; + if(mi == NULL || mi->mod->user_load == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->user_load)(mi, user); + if(ret != 0) + break; + } + + log_debug(ZONE, "user-load chain returning %d", ret); + + return ret; +} + +/** create user */ +int mm_user_create(mm_t mm, jid_t jid) { + int n; + mod_instance_t mi; + int ret = 0; + + log_debug(ZONE, "dispatching user-create chain"); + + for(n = 0; n < mm->nuser_create; n++) { + mi = mm->user_create[n]; + if(mi == NULL || mi->mod->user_create == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + ret = (mi->mod->user_create)(mi, jid); + if(ret != 0) + break; + } + + log_debug(ZONE, "user-create chain returning %d", ret); + + return ret; +} + +/** delete user */ +void mm_user_delete(mm_t mm, jid_t jid) { + int n; + mod_instance_t mi; + + log_debug(ZONE, "dispatching user-delete chain"); + + for(n = 0; n < mm->nuser_delete; n++) { + mi = mm->user_delete[n]; + if(mi == NULL || mi->mod->user_delete == NULL) { + log_debug(ZONE, "module %s has no handler for this chain", mi->mod->name); + continue; + } + + log_debug(ZONE, "calling module %s", mi->mod->name); + + (mi->mod->user_delete)(mi, jid); + } + + log_debug(ZONE, "user-delete chain returning"); +} diff --git a/sm/mod_active.c b/sm/mod_active.c new file mode 100644 index 00000000..32385ff9 --- /dev/null +++ b/sm/mod_active.c @@ -0,0 +1,80 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file sm/mod_active.c + * @brief active user management + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.8 $ + */ + +#include "sm.h" + +static int _active_user_load(mod_instance_t mi, user_t user) { + os_t os; + os_object_t o; + + /* get their active status */ + if(storage_get(user->sm->st, "active", jid_user(user->jid), NULL, &os) == st_SUCCESS && os_iter_first(os)) { + o = os_iter_object(os); + os_object_get_time(os, o, "time", &user->active); + os_free(os); + } else + /* can't load them if they're inactive */ + return 1; + + return 0; +} + +static int _active_user_create(mod_instance_t mi, jid_t jid) { + time_t t; + os_t os; + os_object_t o; + + log_debug(ZONE, "activating user %s", jid_user(jid)); + + t = time(NULL); + + os = os_new(); + o = os_object_new(os); + os_object_put_time(o, "time", &t); + storage_put(mi->sm->st, "active", jid_user(jid), os); + os_free(os); + + return 0; +} + +static void _active_user_delete(mod_instance_t mi, jid_t jid) { + log_debug(ZONE, "deactivating user %s", jid_user(jid)); + + storage_delete(mi->sm->st, "active", jid_user(jid), NULL); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->user_load = _active_user_load; + mod->user_create = _active_user_create; + mod->user_delete = _active_user_delete; + + return 0; +} diff --git a/sm/mod_announce.c b/sm/mod_announce.c new file mode 100644 index 00000000..af7d51aa --- /dev/null +++ b/sm/mod_announce.c @@ -0,0 +1,316 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_announce.c + * @brief announce (broadcast) messages + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.23 $ + */ + +/* + * message to host/announce goes to all online sessions and to offline users next time they connect + * message to host/announce/online goes to all online sessions + */ + +typedef struct moddata_st { + nad_t nad; + int loaded; + time_t t; + os_t tos; + int index; +} *moddata_t; + +static void _announce_load(module_t mod, moddata_t data) { + st_ret_t ret; + os_t os; + os_object_t o; + nad_t nad; + int ns, elem, attr; + char timestamp[18], telem[5]; + struct tm tm; + + /* struct tm can vary in size depending on platform */ + memset(&tm, 0, sizeof(struct tm)); + + data->loaded = 1; + + /* load the current message */ + if((ret = storage_get(mod->mm->sm->st, "motd-message", mod->mm->sm->id, NULL, &os)) == st_SUCCESS) { + os_iter_first(os); + o = os_iter_object(os); + if(os_object_get_nad(os, o, "xml", &nad)) { + /* Copy the nad, as the original is freed when the os is freed below */ + data->nad = nad_copy(nad); + if((ns = nad_find_scoped_namespace(data->nad, uri_DELAY, NULL)) >= 0 && + (elem = nad_find_elem(data->nad, 1, ns, "x", 1)) >= 0 && + (attr = nad_find_attr(data->nad, elem, -1, "stamp", NULL)) >= 0) { + snprintf(timestamp, 18, "%.*s", NAD_AVAL_L(data->nad, attr), NAD_AVAL(data->nad, attr)); + + /* year */ + telem[0] = timestamp[0]; + telem[1] = timestamp[1]; + telem[2] = timestamp[2]; + telem[3] = timestamp[3]; + telem[4] = '\0'; + tm.tm_year = atoi(telem) - 1900; + + /* month */ + telem[0] = timestamp[4]; + telem[1] = timestamp[5]; + telem[2] = '\0'; + tm.tm_mon = atoi(telem) - 1; + + /* day */ + telem[0] = timestamp[6]; + telem[1] = timestamp[7]; + tm.tm_mday = atoi(telem); + + /* hour */ + telem[0] = timestamp[9]; + telem[1] = timestamp[10]; + tm.tm_hour = atoi(telem); + + /* minute */ + telem[0] = timestamp[12]; + telem[1] = timestamp[13]; + tm.tm_min = atoi(telem); + + /* second */ + telem[0] = timestamp[15]; + telem[1] = timestamp[16]; + tm.tm_sec = atoi(telem); + + data->t = mktime(&tm); + } + } + + os_free(os); + } + + if(data->tos != NULL) + os_free(data->tos); + data->tos = os_new(); + os_object_put(os_object_new(data->tos), "time", &data->t, os_type_INTEGER); +} + +static mod_ret_t _announce_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) { + module_t mod = mi->mod; + moddata_t data = (moddata_t) mod->private; + time_t t; + nad_t nad; + pkt_t motd; + os_t os; + os_object_t o; + + /* try to load data if we haven't yet */ + if(data->nad == NULL) { + if(data->loaded) + return mod_PASS; /* nothing to give them */ + _announce_load(mod, data); + if(data->nad == NULL) + return mod_PASS; + } + + /* if they're becoming available for the first time */ + if(pkt->type == pkt_PRESENCE && pkt->to == NULL && sess->user->top == NULL) { + /* load the time of the last motd they got */ + if((time_t) sess->user->module_data[mod->index] == 0 && + storage_get(sess->user->sm->st, "motd-times", jid_user(sess->jid), NULL, &os) == st_SUCCESS) { + os_iter_first(os); + o = os_iter_object(os); + os_object_get_time(os, o, "time", &t); + sess->user->module_data[mod->index] = (void *) t; + os_free(os); + } + + /* they've seen this one */ + if((time_t) sess->user->module_data[mod->index] >= data->t) + return mod_PASS; + + /* a-delivering we go */ + log_debug(ZONE, "delivering stored motd to %s", jid_full(sess->jid)); + + nad = nad_copy(data->nad); + nad_set_attr(nad, 1, -1, "to", jid_full(sess->jid), strlen(jid_full(sess->jid))); + nad_set_attr(nad, 1, -1, "from", mod->mm->sm->id, strlen(mod->mm->sm->id)); + + motd = pkt_new(mod->mm->sm, nad); + if(motd == NULL) { + log_debug(ZONE, "invalid stored motd, not delivering"); + nad_free(nad); + } else + pkt_router(motd); + + sess->user->module_data[mod->index] = (void *) data->t; + storage_replace(sess->user->sm->st, "motd-times", jid_user(sess->jid), NULL, data->tos); + } + + return mod_PASS; +} + +static void _announce_broadcast_user(xht users, const char *key, void *val, void *arg) { + user_t user = (user_t) val; + moddata_t data = (moddata_t) arg; + sess_t sess; + nad_t nad; + + for(sess = user->sessions; sess != NULL; sess = sess->next) { + if((!sess->available && !sess->invisible) || sess->pri < 0) + continue; + + log_debug(ZONE, "resending to '%s'", jid_full(sess->jid)); + + nad = nad_copy(data->nad); + nad_set_attr(nad, 1, -1, "to", jid_full(sess->jid), strlen(jid_full(sess->jid))); + nad_set_attr(nad, 1, -1, "from", user->sm->id, strlen(user->sm->id)); + + pkt_router(pkt_new(user->sm, nad)); + + sess->user->module_data[data->index] = (void *) data->t; + storage_replace(sess->user->sm->st, "motd-times", jid_user(sess->jid), NULL, data->tos); + } +} + +static mod_ret_t _announce_pkt_sm(mod_instance_t mi, pkt_t pkt) { + module_t mod = mi->mod; + moddata_t data = (moddata_t) mod->private; + pkt_t store; + nad_t nad; + time_t t; + os_t os; + os_object_t o; + st_ret_t ret; + + /* time of this packet */ + t = time(NULL); + + /* we want messages addressed to /announce */ + if(pkt->type != pkt_MESSAGE || strlen(pkt->to->resource) < 8 || strncmp(pkt->to->resource, "announce", 8) != 0) + return mod_PASS; + + /* make sure they're allowed */ + if(!aci_check(mod->mm->sm->acls, "broadcast", pkt->from)) { + log_debug(ZONE, "not allowing broadcast from %s", jid_full(pkt->from)); + return -stanza_err_FORBIDDEN; + } + + if(pkt->to->resource[8] == '\0') { + log_debug(ZONE, "storing message for announce later"); + + store = pkt_dup(pkt, NULL, NULL); + + pkt_delay(store, t, mod->mm->sm->id); + + /* prepare for storage */ + os = os_new(); + o = os_object_new(os); + + os_object_put(o, "xml", store->nad, os_type_NAD); + + /* store it */ + ret = storage_replace(mod->mm->sm->st, "motd-message", mod->mm->sm->id, NULL, os); + os_free(os); + + switch(ret) { + case st_FAILED: + pkt_free(store); + return -stanza_err_INTERNAL_SERVER_ERROR; + + case st_NOTIMPL: + pkt_free(store); + return -stanza_err_FEATURE_NOT_IMPLEMENTED; + + default: + break; + } + + /* replace our local copy */ + if(data->nad != NULL) + nad_free(data->nad); + data->nad = store->nad; + + store->nad = NULL; + pkt_free(store); + + /* update timestamp */ + data->t = t; + if(data->tos != NULL) + os_free(data->tos); + data->tos = os_new(); + os_object_put(os_object_new(data->tos), "time", &t, os_type_INTEGER); + } + + else if(strcmp(&(pkt->to->resource[8]), "/online") != 0) { + log_debug(ZONE, "unknown announce resource '%s'", pkt->to->resource); + pkt_free(pkt); + return mod_HANDLED; + } + + log_debug(ZONE, "broadcasting message to all sessions"); + + /* hack */ + nad = data->nad; + data->nad = pkt->nad; + xhash_walk(mod->mm->sm->users, _announce_broadcast_user, (void *) data); + data->nad = nad; + + /* done */ + pkt_free(pkt); + + return mod_HANDLED; +} + +static void _announce_user_delete(mod_instance_t mi, jid_t jid) { + log_debug(ZONE, "deleting motd time for %s", jid_user(jid)); + + storage_delete(mi->sm->st, "motd-times", jid_user(jid), NULL); +} + +static void _announce_free(module_t mod) { + moddata_t data = (moddata_t) mod->private; + + if(data->nad != NULL) nad_free(data->nad); + if(data->tos != NULL) os_free(data->tos); + free(data); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + moddata_t data; + + if(mod->init) return 0; + + data = (moddata_t) malloc(sizeof(struct moddata_st)); + memset(data, 0, sizeof(struct moddata_st)); + + mod->private = (void *) data; + + data->index = mod->index; + + mod->in_sess = _announce_in_sess; + mod->pkt_sm = _announce_pkt_sm; + mod->user_delete = _announce_user_delete; + mod->free = _announce_free; + + return 0; +} diff --git a/sm/mod_deliver.c b/sm/mod_deliver.c new file mode 100644 index 00000000..e93e4558 --- /dev/null +++ b/sm/mod_deliver.c @@ -0,0 +1,100 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_deliver.c + * @brief packet delivery + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.18 $ + */ + +static mod_ret_t _deliver_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) +{ + /* all messages and everything thats not for us gets sent out */ + if(pkt->type & pkt_MESSAGE || (pkt->to != NULL && jid_compare_full(pkt->to, sess->jid) != 0)) + { + /* ensure from is set correctly if not already by client */ + if(pkt->from == NULL || jid_compare_user(pkt->from, sess->jid) != 0) + { + if(pkt->from != NULL) + jid_free(pkt->from); + + pkt->from = jid_dup(sess->jid); + nad_set_attr(pkt->nad, 1, -1, "from", jid_full(pkt->from), 0); + } + + /* no to address means its to us */ + if(pkt->to == NULL) + { + pkt->to = jid_dup(sess->jid); + nad_set_attr(pkt->nad, 1, -1, "to", jid_full(pkt->to), 0); + } + + pkt_router(pkt); + + return mod_HANDLED; + } + + return mod_PASS; +} + +static mod_ret_t _deliver_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) +{ + sess_t sess; + + /* if there's a resource, send it direct */ + if(*pkt->to->resource != '\0') { + /* find the session */ + sess = sess_match(user, pkt->to->resource); + + /* and send it straight there */ + if(sess != NULL) { + pkt_sess(pkt, sess); + return mod_HANDLED; + } + + /* no session */ + if(pkt->type & pkt_PRESENCE) { + pkt_free(pkt); + return mod_HANDLED; + + } else if(pkt->type & pkt_IQ) + return -stanza_err_RECIPIENT_UNAVAILABLE; + + /* unmatched messages will fall through (XMPP-IM r20 s11 rule 2) */ + } + + return mod_PASS; +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->in_sess = _deliver_in_sess; + mod->pkt_user = _deliver_pkt_user; + + feature_register(mod->mm->sm, "message"); + + return 0; +} diff --git a/sm/mod_disco.c b/sm/mod_disco.c new file mode 100644 index 00000000..1bc03a23 --- /dev/null +++ b/sm/mod_disco.c @@ -0,0 +1,737 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_disco.c + * @brief service discovery + * @author Robert Norris + * $Date: 2005/09/09 05:34:13 $ + * $Revision: 1.35 $ + */ + +/** holder for a single service */ +typedef struct service_st *service_t; +struct service_st { + jid_t jid; + + char name[257]; + + char category[257]; + char type[257]; + + xht features; +}; + +/** all the current disco data */ +typedef struct disco_st *disco_t; +struct disco_st { + /** identity */ + char *category; + char *type; + char *name; + + /** compatibility */ + int agents; + int browse; + + /** the lists */ + xht dyn; + xht stat; + + /** unified list */ + xht un; + + /** cached result packets */ + pkt_t disco_info_result; + pkt_t disco_items_result; + pkt_t agents_result; + pkt_t browse_result; +}; + +/* union for xhash_iter_get to comply with strict-alias rules for gcc3 */ +union xhashv +{ + void **val; + service_t *svc_val; + sess_t *sess_val; + const char **char_val; +}; + +/** put val into arg */ +static void _disco_unify_walker(xht list, const char *key, void *val, void *arg) { + service_t svc = (service_t) val; + xht dest = (xht) arg; + + /* if its already there, skip this one */ + if(xhash_get(dest, jid_full(svc->jid)) != NULL) + return; + + log_debug(ZONE, "unify: %s", jid_full(svc->jid)); + + xhash_put(dest, jid_full(svc->jid), (void *) svc); +} + +/** unify the contest of dyn and stat */ +static void _disco_unify_lists(disco_t d) { + log_debug(ZONE, "unifying lists"); + + if(d->un != NULL) + xhash_free(d->un); + + d->un = xhash_new(101); + + /* dynamic overrieds static */ + xhash_walk(d->dyn, _disco_unify_walker, (void *) d->un); + xhash_walk(d->stat, _disco_unify_walker, (void *) d->un); +} + +/** build a disco items result, known services */ +static pkt_t _disco_items_result(module_t mod, disco_t d) { + pkt_t pkt; + int ns; + service_t svc; + union xhashv xhv; + + pkt = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL); + ns = nad_add_namespace(pkt->nad, uri_DISCO_ITEMS, NULL); + nad_append_elem(pkt->nad, ns, "query", 2); + + if(xhash_iter_first(d->un)) + do { + xhv.svc_val = &svc; + xhash_iter_get(d->un, NULL, xhv.val); + + nad_append_elem(pkt->nad, ns, "item", 3); + nad_append_attr(pkt->nad, -1, "jid", jid_full(svc->jid)); + + if(svc->name[0] != '\0') + nad_append_attr(pkt->nad, -1, "name", svc->name); + } while(xhash_iter_next(d->un)); + + return pkt; +} + +/** build a disco info result */ +static pkt_t _disco_info_result(module_t mod, disco_t d) { + pkt_t pkt; + int ns; + const char *key; + + pkt = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL); + ns = nad_add_namespace(pkt->nad, uri_DISCO_INFO, NULL); + nad_append_elem(pkt->nad, ns, "query", 2); + + /* identity */ + nad_append_elem(pkt->nad, ns, "identity", 3); + nad_append_attr(pkt->nad, -1, "category", d->category); + nad_append_attr(pkt->nad, -1, "type", d->type); + nad_append_attr(pkt->nad, -1, "name", d->name); + + /* fill in our features */ + if(xhash_iter_first(mod->mm->sm->features)) + do { + xhash_iter_get(mod->mm->sm->features, &key, NULL); + + nad_append_elem(pkt->nad, ns, "feature", 3); + nad_append_attr(pkt->nad, -1, "var", (char *) key); + } while(xhash_iter_next(mod->mm->sm->features)); + + return pkt; +} + +/** build an agents result */ +static pkt_t _disco_agents_result(module_t mod, disco_t d) { + pkt_t pkt; + int ns; + const char *key; + service_t svc; + union xhashv xhv; + + pkt = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL); + ns = nad_add_namespace(pkt->nad, uri_AGENTS, NULL); + nad_append_elem(pkt->nad, ns, "query", 2); + + /* fill in the items */ + if(xhash_iter_first(d->un)) + do { + xhv.svc_val = &svc; + xhash_iter_get(d->un, &key, xhv.val); + + nad_append_elem(pkt->nad, ns, "agent", 3); + nad_append_attr(pkt->nad, -1, "jid", jid_full(svc->jid)); + + if(svc->name[0] != '\0') { + nad_append_elem(pkt->nad, ns, "name", 4); + nad_append_cdata(pkt->nad, svc->name, strlen(svc->name), 5); + } + + nad_append_elem(pkt->nad, ns, "service", 4); + nad_append_cdata(pkt->nad, svc->type, strlen(svc->type), 5); + + /* map features to the old agent flags */ + if(xhash_get(svc->features, uri_REGISTER) != NULL) + nad_append_elem(pkt->nad, ns, "register", 4); + if(xhash_get(svc->features, uri_SEARCH) != NULL) + nad_append_elem(pkt->nad, ns, "search", 4); + if(xhash_get(svc->features, uri_GATEWAY) != NULL) + nad_append_elem(pkt->nad, ns, "transport", 4); + + /* conference gets special treatment */ + if(strcmp(svc->category, "conference") == 0) + nad_append_elem(pkt->nad, ns, "groupchat", 4); + } while(xhash_iter_next(d->un)); + + return pkt; +} + +/** build a browse result */ +static pkt_t _disco_browse_result(module_t mod, disco_t d) { + pkt_t pkt; + int ns; + const char *key; + service_t svc; + union xhashv xhv; + + pkt = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL); + ns = nad_add_namespace(pkt->nad, uri_BROWSE, NULL); + nad_append_elem(pkt->nad, ns, "service", 2); + + nad_append_attr(pkt->nad, -1, "jid", mod->mm->sm->id); + nad_append_attr(pkt->nad, -1, "type", "jabber"); + + /* fill in our features */ + if(xhash_iter_first(mod->mm->sm->features)) + do { + xhash_iter_get(mod->mm->sm->features, &key, NULL); + + /* hackishly seperate generic features from namespaces */ + if(!((strlen(key) >= 7 && (strncmp(key, "jabber:", 7) == 0 || strncmp(key, "http://", 7) == 0)) || strcmp(key, "vcard-temp") == 0)) + continue; + + nad_append_elem(pkt->nad, ns, "ns", 3); + nad_append_cdata(pkt->nad, (char *) key, strlen(key), 4); + } while(xhash_iter_next(mod->mm->sm->features)); + + /* fill in the items */ + if(xhash_iter_first(d->un)) + do { + xhv.svc_val = &svc; + xhash_iter_get(d->un, NULL, xhv.val); + + if(strcmp(svc->category, "gateway") == 0) + nad_append_elem(pkt->nad, ns, "service", 3); + else + nad_append_elem(pkt->nad, ns, svc->category, 3); + + nad_append_attr(pkt->nad, -1, "jid", jid_full(svc->jid)); + + if(svc->name[0] != '\0') + nad_append_attr(pkt->nad, -1, "name", svc->name); + + nad_append_attr(pkt->nad, -1, "type", svc->type); + + /* service features */ + if(xhash_iter_first(svc->features)) + do { + xhash_iter_get(svc->features, &key, NULL); + + /* hackishly seperate generic features from namespaces */ + if(!((strlen(key) >= 7 && (strncmp(key, "jabber:", 7) == 0 || strncmp(key, "http://", 7) == 0)) || strcmp(key, "vcard-temp") == 0)) + continue; + + nad_append_elem(pkt->nad, ns, "ns", 4); + nad_append_cdata(pkt->nad, (char *) key, strlen(key), 5); + } while(xhash_iter_next(svc->features)); + } while(xhash_iter_next(d->un)); + + return pkt; +} + +/** generate cached result packets */ +static void _disco_generate_packets(module_t mod, disco_t d) { + log_debug(ZONE, "regenerating packets"); + + if(d->disco_items_result != NULL) + pkt_free(d->disco_items_result); + d->disco_items_result = _disco_items_result(mod, d); + + if(d->disco_info_result != NULL) + pkt_free(d->disco_info_result); + d->disco_info_result = _disco_info_result(mod, d); + + if(d->agents) { + if(d->agents_result != NULL) + pkt_free(d->agents_result); + d->agents_result = _disco_agents_result(mod, d); + } + + if(d->browse) { + if(d->browse_result != NULL) + pkt_free(d->browse_result); + d->browse_result = _disco_browse_result(mod, d); + } +} + +/** catch responses and populate the table */ +static mod_ret_t _disco_pkt_sm_populate(mod_instance_t mi, pkt_t pkt) +{ + module_t mod = mi->mod; + disco_t d = (disco_t) mod->private; + int ns, qelem, elem, attr; + service_t svc; + + /* it has to come from the service itself - don't want any old user messing with the table */ + if(pkt->from->node[0] != '\0' || pkt->from->resource[0] != '\0') + { + log_debug(ZONE, "disco response from %s, not allowed", jid_full(pkt->from)); + return -stanza_err_NOT_ALLOWED; + } + + ns = nad_find_scoped_namespace(pkt->nad, uri_DISCO_INFO, NULL); + qelem = nad_find_elem(pkt->nad, 1, ns, "query", 1); + + elem = nad_find_elem(pkt->nad, qelem, ns, "identity", 1); + if(elem < 0) + return -stanza_err_BAD_REQUEST; + + /* see if we already have this service */ + svc = xhash_get(d->dyn, jid_full(pkt->from)); + if(svc == NULL) + { + /* make a new one */ + svc = (service_t) malloc(sizeof(struct service_st)); + memset(svc, 0, sizeof(struct service_st)); + + svc->jid = jid_dup(pkt->from); + + svc->features = xhash_new(9); + + /* link it in */ + xhash_put(d->dyn, jid_full(svc->jid), (void *) svc); + + /* unify */ + _disco_unify_lists(d); + } + + /* fill in the name */ + attr = nad_find_attr(pkt->nad, elem, -1, "name", NULL); + if(attr < 0) + svc->name[0] = '\0'; + else + snprintf(svc->name, 257, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr)); + + /* category and type */ + attr = nad_find_attr(pkt->nad, elem, -1, "category", NULL); + if(attr >= 0) + snprintf(svc->category, 257, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr)); + else + strcpy(svc->category, "unknown"); + + attr = nad_find_attr(pkt->nad, elem, -1, "type", NULL); + if(attr >= 0) + snprintf(svc->type, 257, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr)); + else + strcpy(svc->type, "unknown"); + + /* features */ + elem = nad_find_elem(pkt->nad, qelem, -1, "feature", 1); + while(elem >= 0) + { + attr = nad_find_attr(pkt->nad, elem, -1, "var", NULL); + if(attr < 0) + { + elem = nad_find_elem(pkt->nad, elem, -1, "feature", 0); + continue; + } + + xhash_put(svc->features, pstrdupx(xhash_pool(svc->features), NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)), (void *) 1); + + elem = nad_find_elem(pkt->nad, elem, -1, "feature", 0); + } + + /* regenerate packets */ + _disco_generate_packets(mod, d); + + pkt_free(pkt); + + return mod_HANDLED; +} + +/** build a disco items result, active sessions */ +static void _disco_sessions_result(module_t mod, disco_t d, pkt_t pkt) { + int ns; + sess_t sess; + union xhashv xhv; + + ns = nad_add_namespace(pkt->nad, uri_DISCO_ITEMS, NULL); + nad_append_elem(pkt->nad, ns, "query", 2); + nad_append_attr(pkt->nad, -1, "node", "sessions"); + + if(xhash_iter_first(mod->mm->sm->sessions)) + do { + xhv.sess_val = &sess; + xhash_iter_get(mod->mm->sm->sessions, NULL, xhv.val); + + nad_append_elem(pkt->nad, ns, "item", 3); + nad_append_attr(pkt->nad, -1, "jid", jid_full(sess->jid)); + nad_append_attr(pkt->nad, -1, "name", "Active session"); + } while(xhash_iter_next(mod->mm->sm->sessions)); +} + +/** catch responses and populate the table; respond to requests */ +static mod_ret_t _disco_pkt_sm(mod_instance_t mi, pkt_t pkt) { + module_t mod = mi->mod; + disco_t d = (disco_t) mod->private; + pkt_t result; + int node; + + /* disco info results go to a seperate function */ + if(pkt->type == pkt_IQ_RESULT && pkt->ns == ns_DISCO_INFO) + return _disco_pkt_sm_populate(mi, pkt); + + /* we want disco, browse or agents gets */ + if(pkt->type != pkt_IQ || !(pkt->ns == ns_DISCO_INFO || pkt->ns == ns_DISCO_ITEMS || pkt->ns == ns_BROWSE || pkt->ns == ns_AGENTS)) + return mod_PASS; + + /* generate the caches if we haven't yet */ + if(d->disco_info_result == NULL) + _disco_generate_packets(mod, d); + + /* they want to know about us */ + if(pkt->ns == ns_DISCO_INFO) { + result = pkt_dup(d->disco_info_result, jid_full(pkt->from), jid_full(pkt->to)); + pkt_id(pkt, result); + pkt_free(pkt); + + /* off it goes */ + pkt_router(result); + + return mod_HANDLED; + } + + /* handle agents */ + if(pkt->ns == ns_AGENTS) { + /* make sure we're supporting compat */ + if(!d->agents) + return -stanza_err_NOT_ALLOWED; + + result = pkt_dup(d->agents_result, jid_full(pkt->from), jid_full(pkt->to)); + pkt_id(pkt, result); + pkt_free(pkt); + + /* off it goes */ + pkt_router(result); + + return mod_HANDLED; + } + + /* handle browse */ + if(pkt->ns == ns_BROWSE) { + /* make sure we're supporting compat */ + if(!d->browse) + return -stanza_err_NOT_ALLOWED; + + result = pkt_dup(d->browse_result, jid_full(pkt->from), jid_full(pkt->to)); + pkt_id(pkt, result); + pkt_free(pkt); + + /* off it goes */ + pkt_router(result); + + return mod_HANDLED; + } + + /* they want to know who we know about */ + node = nad_find_attr(pkt->nad, 2, -1, "node", NULL); + if(node < 0) { + /* no node, so toplevel services */ + result = pkt_dup(d->disco_items_result, jid_full(pkt->from), jid_full(pkt->to)); + pkt_id(pkt, result); + pkt_free(pkt); + + /* if they have privs, them show them any administrative things they can disco to */ + if(aci_check(mod->mm->sm->acls, "disco", result->to)) { + nad_append_elem(result->nad, NAD_ENS(result->nad, 2), "item", 3); + nad_append_attr(result->nad, -1, "jid", mod->mm->sm->id); + nad_append_attr(result->nad, -1, "node", "sessions"); + nad_append_attr(result->nad, -1, "name", "Active sessions"); + } + + pkt_router(result); + + return mod_HANDLED; + } + + /* active sessions */ + if(NAD_AVAL_L(pkt->nad, node) == 8 && strncmp("sessions", NAD_AVAL(pkt->nad, node), 8) == 0) { + /* priviliged op, make sure they're allowed */ + if(!aci_check(mod->mm->sm->acls, "disco", pkt->from)) + return -stanza_err_ITEM_NOT_FOUND; /* we never advertised it, so we can pretend its not here */ + + result = pkt_create(mod->mm->sm, "iq", "result", jid_full(pkt->from), jid_full(pkt->to)); + pkt_id(pkt, result); + pkt_free(pkt); + + _disco_sessions_result(mod, d, result); + + /* off it goes */ + pkt_router(result); + + return mod_HANDLED; + } + + /* I dunno what they're asking for */ + return -stanza_err_ITEM_NOT_FOUND; +} + +/** legacy support for agents requests from sessions */ +static mod_ret_t _disco_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) { + module_t mod = mi->mod; + disco_t d = (disco_t) mod->private; + pkt_t result; + + /* we want agents gets */ + if(pkt->type != pkt_IQ || pkt->ns != ns_AGENTS || pkt->to != NULL) + return mod_PASS; + + /* fail if its not enabled */ + if(!d->agents) + return -stanza_err_NOT_ALLOWED; + + /* generate the caches if we haven't yet */ + if(d->disco_info_result == NULL) + _disco_generate_packets(mod, d); + + /* pre-canned response */ + result = pkt_dup(d->agents_result, NULL, NULL); + pkt_id(pkt, result); + pkt_free(pkt); + + /* off it goes */ + pkt_sess(result, sess); + + return mod_HANDLED; +} + +/** update the table for component changes */ +static mod_ret_t _disco_pkt_router(mod_instance_t mi, pkt_t pkt) +{ + module_t mod = mi->mod; + disco_t d = (disco_t) mod->private; + service_t svc; + pkt_t request; + int ns; + + /* we want advertisements with a from address */ + if(pkt->from == NULL || !(pkt->rtype & route_ADV)) + return mod_PASS; + + /* component online */ + if(pkt->rtype == route_ADV) + { + log_debug(ZONE, "presence from component %s, issuing discovery request", jid_full(pkt->from)); + + /* new disco get packet */ + request = pkt_create(mod->mm->sm, "iq", "get", jid_full(pkt->from), mod->mm->sm->id); + pkt_id_new(request); + ns = nad_add_namespace(request->nad, uri_DISCO_INFO, NULL); + nad_append_elem(request->nad, ns, "query", 2); + + pkt_router(request); + + /* done with this */ + pkt_free(pkt); + + return mod_HANDLED; + } + + /* it went away. find it and remove it */ + svc = xhash_get(d->dyn, jid_full(pkt->from)); + if(svc != NULL) + { + log_debug(ZONE, "dropping entry for %s", jid_full(pkt->from)); + + xhash_zap(d->dyn, jid_full(pkt->from)); + + jid_free(svc->jid); + xhash_free(svc->features); + free(svc); + + /* unify */ + _disco_unify_lists(d); + _disco_generate_packets(mod, d); + } + + /* done */ + pkt_free(pkt); + + return mod_HANDLED; +} + +static void _disco_free_walker(xht h, const char *key, void *val, void *arg) { + service_t svc = (service_t) val; + + jid_free(svc->jid); + xhash_free(svc->features); + free(svc); +} + +static void _disco_free(module_t mod) { + disco_t d = (disco_t) mod->private; + + xhash_walk(d->stat, _disco_free_walker, NULL); + xhash_walk(d->dyn, _disco_free_walker, NULL); + + xhash_free(d->stat); + xhash_free(d->dyn); + xhash_free(d->un); + + if(d->disco_info_result != NULL) pkt_free(d->disco_info_result); + if(d->disco_items_result != NULL) pkt_free(d->disco_items_result); + if(d->agents_result != NULL) pkt_free(d->agents_result); + if(d->browse_result != NULL) pkt_free(d->browse_result); + + free(d); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) +{ + module_t mod = mi->mod; + disco_t d; + nad_t nad; + int items, item, jid, name, category, type, ns; + service_t svc; + + if(mod->init) return 0; + + log_debug(ZONE, "disco module init"); + + d = (disco_t) malloc(sizeof(struct disco_st)); + memset(d, 0, sizeof(struct disco_st)); + + /* new hashes to store the lists in */ + d->dyn = xhash_new(51); + d->stat = xhash_new(51); + + /* identity */ + d->category = config_get_one(mod->mm->sm->config, "discovery.identity.category", 0); + if(d->category == NULL) d->category = "server"; + d->type = config_get_one(mod->mm->sm->config, "discovery.identity.type", 0); + if(d->type == NULL) d->type = "im"; + d->name = config_get_one(mod->mm->sm->config, "discovery.identity.name", 0); + if(d->name == NULL) d->name = "Jabber IM server"; + + /* agents compatibility */ + d->agents = (int) config_get(mod->mm->sm->config, "discovery.agents"); + + /* browse compatibility */ + /* CODE READERS, NOTE WELL: Browse is very deprecated. Don't use this :) */ + d->browse = (int) config_get(mod->mm->sm->config, "discovery.browse"); + + if(d->agents) + log_debug(ZONE, "agents compat enabled"); + if(d->browse) + log_debug(ZONE, "browse compat enabled"); + + /* our data */ + mod->private = (void *) d; + + /* our handlers */ + mod->pkt_sm = _disco_pkt_sm; + mod->in_sess = _disco_in_sess; + mod->pkt_router = _disco_pkt_router; + mod->free = _disco_free; + + nad = mod->mm->sm->config->nad; + + /* we support a number of things */ + feature_register(mod->mm->sm, uri_DISCO); + if(d->agents) + feature_register(mod->mm->sm, uri_AGENTS); + if(d->browse) + feature_register(mod->mm->sm, uri_BROWSE); + + /* populate the static list from the config file */ + if((items = nad_find_elem(nad, 0, -1, "discovery", 1)) < 0 || (items = nad_find_elem(nad, items, -1, "items", 1)) < 0) + return 0; + + item = nad_find_elem(nad, items, -1, "item", 1); + while(item >= 0) + { + /* jid is required */ + jid = nad_find_attr(nad, item, -1, "jid", NULL); + if(jid < 0) + { + item = nad_find_elem(nad, item, -1, "item", 0); + continue; + } + + /* new service */ + svc = (service_t) malloc(sizeof(struct service_st)); + memset(svc, 0, sizeof(struct service_st)); + + svc->features = xhash_new(13); + + svc->jid = jid_new(mod->mm->sm->pc, NAD_AVAL(nad, jid), NAD_AVAL_L(nad, jid)); + + /* link it in */ + xhash_put(d->stat, jid_full(svc->jid), (void *) svc); + + /* copy the name */ + name = nad_find_attr(nad, item, -1, "name", NULL); + if(name >= 0) + snprintf(svc->name, 257, "%.*s", NAD_AVAL_L(nad, name), NAD_AVAL(nad, name)); + + /* category and type */ + category = nad_find_attr(nad, item, -1, "category", NULL); + if(category >= 0) + snprintf(svc->category, 257, "%.*s", NAD_AVAL_L(nad, category), NAD_AVAL(nad, category)); + else + strcpy(svc->category, "unknown"); + + type = nad_find_attr(nad, item, -1, "type", NULL); + if(type >= 0) + snprintf(svc->type, 257, "%.*s", NAD_AVAL_L(nad, type), NAD_AVAL(nad, type)); + else + strcpy(svc->type, "unknown"); + + /* namespaces */ + ns = nad_find_elem(nad, item, -1, "ns", 1); + while(ns >= 0) + { + if(NAD_CDATA_L(nad, ns) > 0) + xhash_put(svc->features, pstrdupx(xhash_pool(svc->features), NAD_CDATA(nad, ns), NAD_CDATA_L(nad, ns)), (void *) 1); + + ns = nad_find_elem(nad, ns, -1, "ns", 0); + } + + item = nad_find_elem(nad, item, -1, "item", 0); + + log_debug(ZONE, "added %s to static list", jid_full(svc->jid)); + } + + /* generate the initial union list */ + _disco_unify_lists(d); + + /* we don't generate the packets here, because the router conn isn't up yet, and so we don't have a nad cache */ + + return 0; +} diff --git a/sm/mod_disco_publish.c b/sm/mod_disco_publish.c new file mode 100644 index 00000000..09a41718 --- /dev/null +++ b/sm/mod_disco_publish.c @@ -0,0 +1,313 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_disco_publish.c + * @brief user info publishing + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.15 $ + */ + +/** holder for a single item */ +typedef struct disco_item_st *disco_item_t; +struct disco_item_st { + jid_t jid; + char name[257]; + char node[257]; + disco_item_t next; +}; + +static mod_ret_t _disco_publish_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) { + module_t mod = mi->mod; + disco_item_t list, di, scan, updi; + pkt_t res; + int ns, elem, attr; + char filter[4096]; + os_t os; + os_object_t o; + + /* can only deal with disco info requests */ + if(pkt->ns != ns_DISCO_ITEMS) + return mod_PASS; + + list = user->module_data[mod->index]; + + /* get */ + if(pkt->type == pkt_IQ) { + res = pkt_create(mod->mm->sm, "iq", "result", jid_full(pkt->from), jid_full(pkt->to)); + pkt_id(pkt, res); + + ns = nad_add_namespace(res->nad, uri_DISCO_INFO, NULL); + nad_append_elem(res->nad, ns, "query", 2); + + pkt_free(pkt); + + for(scan = list; scan != NULL; scan = scan->next) { + nad_append_elem(res->nad, ns, "item", 3); + nad_append_attr(res->nad, -1, "jid", jid_full(scan->jid)); + if(scan->name[0] != '\0') + nad_append_attr(res->nad, -1, "name", scan->name); + if(scan->node[0] != '\0') + nad_append_attr(res->nad, -1, "node", scan->node); + } + + pkt_router(res); + + return mod_HANDLED; + } + + /* only sets from here */ + if(pkt->type != pkt_IQ_SET) + return mod_PASS; + + /* only they can modify their details */ + if(jid_compare_user(pkt->from, user->jid) != 0) + return -stanza_err_FORBIDDEN; + + ns = nad_find_scoped_namespace(pkt->nad, uri_DISCO_INFO, NULL); + + /* extract the items */ + elem = nad_find_elem(pkt->nad, 2, ns, "item", 1); + while(elem >= 0) { + /* jid is required */ + attr = nad_find_attr(pkt->nad, elem, -1, "jid", NULL); + if(attr < 0) { + elem = nad_find_elem(pkt->nad, elem, ns, "item", 0); + continue; /* can't return an error halfway through, otherwise we leave things in an undefined state */ + } + + /* new item */ + di = (disco_item_t) malloc(sizeof(struct disco_item_st)); + memset(di, 0, sizeof(struct disco_item_st)); + + /* jid */ + di->jid = jid_new(mod->mm->sm->pc, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + + /* name */ + attr = nad_find_attr(pkt->nad, elem, -1, "name", NULL); + if(attr >= 0) + strncpy(di->name, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr) > 256 ? 256 : NAD_AVAL_L(pkt->nad, attr)); + + /* node */ + attr = nad_find_attr(pkt->nad, elem, -1, "node", NULL); + if(attr >= 0) + strncpy(di->node, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr) > 256 ? 256 : NAD_AVAL_L(pkt->nad, attr)); + + /* delete it from the list */ + if(nad_find_attr(pkt->nad, elem, -1, "action", "remove") >= 0) { + if(list != NULL) { + updi = NULL; + + /* first on the list */ + if(jid_compare_full(di->jid, list->jid) == 0 && strcmp(di->node, list->node) == 0) { + updi = list; + list = list->next; + user->module_data[mod->index] = list; + } + + /* list guts */ + else { + for(scan = list; scan != NULL && scan->next != NULL && jid_compare_full(di->jid, scan->next->jid) != 0 && strcmp(di->node, scan->next->node) != 0; scan = scan->next); + if(scan->next != NULL) { + updi = scan->next; + scan->next = scan->next->next; + } + } + + /* kill it */ + if(updi != NULL) { + jid_free(updi->jid); + free(updi); + + /* make a filter */ + if(di->node[0] == '\0') + /* filter is (jid=blah) */ + sprintf(filter, "(jid=%i:%s)", strlen(jid_full(di->jid)), jid_full(di->jid)); + else + /* filter is (&(jid=blah)(node=moreblah)) */ + sprintf(filter, "(&(jid=%i:%s)(node=%i:%s))", strlen(jid_full(di->jid)), jid_full(di->jid), strlen(di->node), di->node); + + /* sucks if it fails, but we can't do anything about it anyway */ + storage_delete(mod->mm->sm->st, "disco-items", jid_user(user->jid), filter); + } + } + + /* don't need this anymore */ + jid_free(di->jid); + free(di); + } + + /* update the list */ + else { + /* we're first */ + if(list == NULL) + list = user->module_data[mod->index] = di; + + /* find it if it exists already */ + else { + updi = NULL; + + /* first on the list */ + if(jid_compare_full(di->jid, list->jid) == 0 && strcmp(di->node, list->node) == 0) { + updi = list; + di->next = list->next; + list = user->module_data[mod->index] = di; + } + + /* list guts */ + else { + for(scan = list; scan != NULL && scan->next != NULL && jid_compare_full(di->jid, scan->next->jid) != 0 && strcmp(di->node, scan->next->node) != 0; scan = scan->next); + if(scan->next != NULL) { + updi = scan->next; + scan->next = di; + di->next = scan->next->next; + } + } + + /* didn't find it, just insert at the front */ + if(updi == NULL) { + di->next = list; + list = user->module_data[mod->index] = di; + } + + /* nuke the old one */ + else { + jid_free(updi->jid); + free(updi); + } + } + + /* make a filter */ + if(di->node[0] == '\0') + /* filter is (jid=blah) */ + sprintf(filter, "(jid=%i:%s)", strlen(jid_full(di->jid)), jid_full(di->jid)); + else + /* filter is (&(jid=blah)(node=moreblah)) */ + sprintf(filter, "(&(jid=%i:%s)(node=%i:%s))", strlen(jid_full(di->jid)), jid_full(di->jid), strlen(di->node), di->node); + + /* prepare objects */ + os = os_new(); + o = os_object_new(os); + + os_object_put(o, "jid", jid_full(di->jid), os_type_STRING); + if(di->name[0] != '\0') + os_object_put(o, "name", di->name, os_type_STRING); + if(di->node[0] != '\0') + os_object_put(o, "node", di->node, os_type_STRING); + + storage_replace(mod->mm->sm->st, "disco-items", jid_user(user->jid), filter, os); + + os_free(os); + } + + elem = nad_find_elem(pkt->nad, elem, ns, "item", 0); + } + + res = pkt_create(mod->mm->sm, "iq", "result", jid_full(pkt->from), jid_full(pkt->to)); + pkt_id(pkt, res); + + pkt_free(pkt); + + pkt_router(res); + + return mod_HANDLED; +} + +static void _disco_publish_user_free(disco_item_t *list) { + disco_item_t scan, next; + + scan = *list; + while(scan != NULL) { + log_debug(ZONE, "freeing published disco item %s node %s", jid_full(scan->jid), scan->node); + + next = scan->next; + jid_free(scan->jid); + free(scan); + scan = next; + } +} + +static int _disco_publish_user_load(mod_instance_t mi, user_t user) { + module_t mod = mi->mod; + disco_item_t list = user->module_data[mod->index], scan, next, di; + os_t os; + os_object_t o; + char *str; + + scan = list; + while(scan != NULL) { + next = scan->next; + jid_free(scan->jid); + free(scan); + scan = next; + } + + list = user->module_data[mod->index] = NULL; + + pool_cleanup(user->p, (void (*))(void *) _disco_publish_user_free, &(user->module_data[mod->index])); + + if(storage_get(mod->mm->sm->st, "disco-items", jid_user(user->jid), NULL, &os) != st_SUCCESS) + return 0; + + if(os_iter_first(os)) + do { + o = os_iter_object(os); + + if(os_object_get_str(os, o, "jid", &str)) { + di = (disco_item_t) malloc(sizeof(struct disco_item_st)); + memset(di, 0, sizeof(struct disco_item_st)); + + di->jid = jid_new(mod->mm->sm->pc, str, -1); + + if(os_object_get_str(os, o, "name", &str)) + strncpy(di->name, str, 256); + if(os_object_get_str(os, o, "node", &str)) + strncpy(di->node, str, 256); + + di->next = list; + list = user->module_data[mod->index] = di; + } + } while(os_iter_next(os)); + + os_free(os); + + return 0; +} + +static void _disco_publish_user_delete(mod_instance_t mi, jid_t jid) { + log_debug(ZONE, "deleting published disco items for %s", jid_user(jid)); + + storage_delete(mi->sm->st, "disco-items", jid_user(jid), NULL); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + if(mi->mod->init) return 0; + + log_debug(ZONE, "disco publish module init"); + + /* our handlers */ + mi->mod->pkt_user = _disco_publish_pkt_user; + mi->mod->user_load = _disco_publish_user_load; + mi->mod->user_delete = _disco_publish_user_delete; + + return 0; +} diff --git a/sm/mod_echo.c b/sm/mod_echo.c new file mode 100644 index 00000000..aee89858 --- /dev/null +++ b/sm/mod_echo.c @@ -0,0 +1,52 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_echo.c + * @brief message echo + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.9 $ + */ + +static mod_ret_t _echo_pkt_sm(mod_instance_t mi, pkt_t pkt) +{ + /* we want messages addressed to /echo */ + if(pkt->type != pkt_MESSAGE || strcmp(pkt->to->resource, "echo") != 0) + return mod_PASS; + + log_debug(ZONE, "echo request from %s", jid_full(pkt->from)); + + /* swap to and from and return it */ + pkt_router(pkt_tofrom(pkt)); + + return mod_HANDLED; +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->pkt_sm = _echo_pkt_sm; + + return 0; +} diff --git a/sm/mod_help.c b/sm/mod_help.c new file mode 100644 index 00000000..4051d0d8 --- /dev/null +++ b/sm/mod_help.c @@ -0,0 +1,72 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_help.c + * @brief forward messages to administrators + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.9 $ + */ + +static mod_ret_t _help_pkt_sm(mod_instance_t mi, pkt_t pkt) +{ + module_t mod = mi->mod; + jid_t all, msg, jid; + + /* we want messages addressed to the sm itself */ + if(pkt->type != pkt_MESSAGE || pkt->to->resource[0] != '\0') + return mod_PASS; + + log_debug(ZONE, "help message from %s", jid_full(pkt->from)); + + all = xhash_get(mod->mm->sm->acls, "all"); + msg = xhash_get(mod->mm->sm->acls, "messages"); + + for(jid = all; jid != NULL; jid = jid->next) + { + log_debug(ZONE, "resending to %s", jid_full(jid)); + pkt_router(pkt_dup(pkt, jid_full(jid), mod->mm->sm->id)); + } + + for(jid = msg; jid != NULL; jid = jid->next) + if(!jid_search(all, jid)) + { + log_debug(ZONE, "resending to %s", jid_full(jid)); + pkt_router(pkt_dup(pkt, jid_full(jid), NULL)); + } + + /* !!! autoreply */ + + pkt_free(pkt); + + return mod_HANDLED; +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->pkt_sm = _help_pkt_sm; + + return 0; +} diff --git a/sm/mod_iq_last.c b/sm/mod_iq_last.c new file mode 100644 index 00000000..e63330c2 --- /dev/null +++ b/sm/mod_iq_last.c @@ -0,0 +1,155 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_iq_last.c + * @brief last activity + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.18 $ + */ + +#define uri_LAST "jabber:iq:last" +static int ns_LAST = 0; + +static mod_ret_t _iq_last_pkt_sm(mod_instance_t mi, pkt_t pkt) { + module_t mod = mi->mod; + char uptime[10]; + + /* we only want to play with iq:last gets */ + if(pkt->type != pkt_IQ || pkt->ns != ns_LAST) + return mod_PASS; + + snprintf(uptime, 10, "%d", (int) (time(NULL) - (time_t) mod->private)); + nad_set_attr(pkt->nad, 2, -1, "seconds", uptime, 0); + + /* tell them */ + nad_set_attr(pkt->nad, 1, -1, "type", "result", 6); + pkt_router(pkt_tofrom(pkt)); + + return mod_HANDLED; +} + +static mod_ret_t _iq_last_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) { + char lasttime[10]; + time_t t; + os_t os; + os_object_t o; + st_ret_t ret; + + /* we only want to play with iq:last gets */ + if(pkt->type != pkt_IQ || pkt->ns != ns_LAST) + return mod_PASS; + + /* make sure they're allowed */ + if(!pres_trust(user, pkt->from)) + return -stanza_err_FORBIDDEN; + + /* if they have a leading session, use that */ + if(user->top != NULL) + { + pkt_sess(pkt, user->top); + return mod_HANDLED; + } + + ret = storage_get(user->sm->st, "logout", jid_user(user->jid), NULL, &os); + switch(ret) { + case st_SUCCESS: + t = 0; + + if(os_iter_first(os)) { + o = os_iter_object(os); + + os_object_get_time(os, o, "time", &t); + } + + os_free(os); + + snprintf(lasttime, 10, "%d", (int) (time(NULL) - t)); + nad_set_attr(pkt->nad, 2, -1, "seconds", lasttime, 0); + + nad_set_attr(pkt->nad, 1, -1, "type", "result", 6); + pkt_router(pkt_tofrom(pkt)); + + return mod_HANDLED; + + case st_FAILED: + return -stanza_err_INTERNAL_SERVER_ERROR; + + case st_NOTFOUND: + return -stanza_err_ITEM_NOT_FOUND; + + case st_NOTIMPL: + return -stanza_err_FEATURE_NOT_IMPLEMENTED; + } + + /* we never get here */ + return -stanza_err_INTERNAL_SERVER_ERROR; +} + +static void _iq_last_sess_end(mod_instance_t mi, sess_t sess) { + time_t t; + os_t os; + os_object_t o; + + /* store their logout time */ + t = time(NULL); + + os = os_new(); + o = os_object_new(os); + + os_object_put_time(o, "time", &t); + + storage_replace(sess->user->sm->st, "logout", jid_user(sess->jid), NULL, os); + + os_free(os); +} + +static void _iq_last_user_delete(mod_instance_t mi, jid_t jid) { + log_debug(ZONE, "deleting logout time for %s", jid_user(jid)); + + storage_delete(mi->sm->st, "logout", jid_user(jid), NULL); +} + +static void _iq_last_free(module_t mod) { + sm_unregister_ns(mod->mm->sm, uri_LAST); + feature_unregister(mod->mm->sm, uri_LAST); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->sess_end = _iq_last_sess_end; + mod->pkt_user = _iq_last_pkt_user; + mod->pkt_sm = _iq_last_pkt_sm; + mod->user_delete = _iq_last_user_delete; + mod->free = _iq_last_free; + + /* startup time */ + mod->private = (void *) time(NULL); + + ns_LAST = sm_register_ns(mod->mm->sm, uri_LAST); + feature_register(mod->mm->sm, uri_LAST); + + return 0; +} diff --git a/sm/mod_iq_private.c b/sm/mod_iq_private.c new file mode 100644 index 00000000..b33d93b5 --- /dev/null +++ b/sm/mod_iq_private.c @@ -0,0 +1,192 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_iq_private.c + * @brief private xml storage + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.24 $ + */ + +#define uri_PRIVATE "jabber:iq:private" +static int ns_PRIVATE = 0; + +static mod_ret_t _iq_private_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) { + int ns, elem, target, targetns; + st_ret_t ret; + char filter[4096]; + os_t os; + os_object_t o; + nad_t nad; + pkt_t result; + + /* only handle private sets and gets */ + if((pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET) || pkt->ns != ns_PRIVATE) + return mod_PASS; + + /* we're only interested in no to, to our host, or to us */ + if(pkt->to != NULL && jid_compare_user(sess->jid, pkt->to) != 0 && strcmp(sess->jid->domain, jid_user(pkt->to)) != 0) + return mod_PASS; + + ns = nad_find_scoped_namespace(pkt->nad, uri_PRIVATE, NULL); + elem = nad_find_elem(pkt->nad, 1, ns, "query", 1); + + /* find the first child */ + target = elem + 1; + while(target < pkt->nad->ecur) + { + if(pkt->nad->elems[target].depth > pkt->nad->elems[elem].depth) + break; + + target++; + } + + /* not found, so we're done */ + if(target == pkt->nad->ecur) + return -stanza_err_BAD_REQUEST; + + /* find the target namespace */ + targetns = NAD_ENS(pkt->nad, target); + + /* gotta have a namespace */ + if(targetns < 0) + { + log_debug(ZONE, "no namespace specified"); + return -stanza_err_BAD_REQUEST; + } + + log_debug(ZONE, "processing private request for %.*s", NAD_NURI_L(pkt->nad, targetns), NAD_NURI(pkt->nad, targetns)); + + /* get */ + if(pkt->type == pkt_IQ) { + snprintf(filter, 4096, "(ns=%i:%.*s)", NAD_NURI_L(pkt->nad, targetns), NAD_NURI_L(pkt->nad, targetns), NAD_NURI(pkt->nad, targetns)); + ret = storage_get(sess->user->sm->st, "private", jid_user(sess->jid), filter, &os); + switch(ret) { + case st_SUCCESS: + if(os_iter_first(os)) { + o = os_iter_object(os); + if(os_object_get_nad(os, o, "xml", &nad)) { + result = pkt_new(sess->user->sm, nad_copy(nad)); + if(result != NULL) { + nad_set_attr(result->nad, 1, -1, "type", "result", 6); + + pkt_id(pkt, result); + + pkt_sess(result, sess); + + pkt_free(pkt); + + os_free(os); + + return mod_HANDLED; + } + } + } + + os_free(os); + + /* drop through */ + log_debug(ZONE, "storage_get succeeded, but couldn't make packet, faking st_NOTFOUND"); + + case st_NOTFOUND: + + log_debug(ZONE, "namespace not found, returning"); + + /* + * !!! really, we should just return a 404. 1.4 just slaps a + * result on the packet and sends it back. hurrah for + * legacy namespaces. + */ + nad_set_attr(pkt->nad, 1, -1, "type", "result", 6); + + pkt_sess(pkt_tofrom(pkt), sess); + + return mod_HANDLED; + + case st_FAILED: + return -stanza_err_INTERNAL_SERVER_ERROR; + + case st_NOTIMPL: + return -stanza_err_FEATURE_NOT_IMPLEMENTED; + } + } + + os = os_new(); + o = os_object_new(os); + + snprintf(filter, 4096, "%.*s", NAD_NURI_L(pkt->nad, targetns), NAD_NURI(pkt->nad, targetns)); + os_object_put(o, "ns", filter, os_type_STRING); + os_object_put(o, "xml", pkt->nad, os_type_NAD); + + snprintf(filter, 4096, "(ns=%i:%.*s)", NAD_NURI_L(pkt->nad, targetns), NAD_NURI_L(pkt->nad, targetns), NAD_NURI(pkt->nad, targetns)); + + ret = storage_replace(sess->user->sm->st, "private", jid_user(sess->jid), filter, os); + os_free(os); + + switch(ret) { + case st_FAILED: + return -stanza_err_INTERNAL_SERVER_ERROR; + + case st_NOTIMPL: + return -stanza_err_FEATURE_NOT_IMPLEMENTED; + + default: + result = pkt_create(sess->user->sm, "iq", "result", NULL, NULL); + + pkt_id(pkt, result); + + pkt_sess(result, sess); + + pkt_free(pkt); + + return mod_HANDLED; + } + + /* we never get here */ + return 0; +} + +static void _iq_private_user_delete(mod_instance_t mi, jid_t jid) { + log_debug(ZONE, "deleting private xml storage for %s", jid_user(jid)); + + storage_delete(mi->sm->st, "private", jid_user(jid), NULL); +} + +static void _iq_private_free(module_t mod) { + sm_unregister_ns(mod->mm->sm, uri_PRIVATE); + feature_unregister(mod->mm->sm, uri_PRIVATE); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if (mod->init) return 0; + + mod->in_sess = _iq_private_in_sess; + mod->user_delete = _iq_private_user_delete; + mod->free = _iq_private_free; + + ns_PRIVATE = sm_register_ns(mod->mm->sm, uri_PRIVATE); + feature_register(mod->mm->sm, uri_PRIVATE); + + return 0; +} diff --git a/sm/mod_iq_time.c b/sm/mod_iq_time.c new file mode 100644 index 00000000..4d1e1b13 --- /dev/null +++ b/sm/mod_iq_time.c @@ -0,0 +1,92 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_iq_time.c + * @brief entity time + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.14 $ + */ + +#define uri_TIME "jabber:iq:time" +static int ns_TIME = 0; + +#ifdef HAVE_TZNAME +extern char *tzname[]; +#endif + +static mod_ret_t _iq_time_pkt_sm(mod_instance_t mi, pkt_t pkt) +{ + time_t t; + struct tm *tm; + char buf[64]; + char *c; + + /* we only want to play with iq:time gets */ + if(pkt->type != pkt_IQ || pkt->ns != ns_TIME) + return mod_PASS; + + t = time(NULL); + + sm_timestamp(t, buf); + nad_insert_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 1), "utc", buf); + + tm = localtime(&t); + + strcpy(buf, asctime(tm)); + c = strchr(buf, '\n'); + if(c != NULL) + *c = '\0'; + nad_insert_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 1), "display", buf); + + tzset(); +#if defined(HAVE_STRUCT_TM_TM_ZONE) + nad_insert_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 1), "tz", (char *) tm->tm_zone); +#elif defined(HAVE_TZNAME) + nad_insert_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 1), "tz", tzname[0]); +#endif + + /* tell them */ + nad_set_attr(pkt->nad, 1, -1, "type", "result", 6); + pkt_router(pkt_tofrom(pkt)); + + return mod_HANDLED; +} + +static void _iq_time_free(module_t mod) { + sm_unregister_ns(mod->mm->sm, uri_TIME); + feature_unregister(mod->mm->sm, uri_TIME); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->pkt_sm = _iq_time_pkt_sm; + mod->free = _iq_time_free; + + ns_TIME = sm_register_ns(mod->mm->sm, uri_TIME); + feature_register(mod->mm->sm, uri_TIME); + + return 0; +} diff --git a/sm/mod_iq_vcard.c b/sm/mod_iq_vcard.c new file mode 100644 index 00000000..a42ae595 --- /dev/null +++ b/sm/mod_iq_vcard.c @@ -0,0 +1,299 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_iq_vcard.c + * @brief user profiles (vcard) + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.25 $ + */ + +#define uri_VCARD "vcard-temp" +static int ns_VCARD = 0; + +/** + * these are the vcard attributes that gabber supports. they're also + * all strings, and thus easy to automate. there might be more in + * regular use, we need to check that out. one day, when we're all + * using real foaf profiles, we'll have bigger things to worry about :) + */ + +static char *_iq_vcard_map[] = { + "FN", "fn", + "NICKNAME", "nickname", + "URL", "url", + "TEL/NUMBER", "tel", + "EMAIL/USERID", "email", + "TITLE", "title", + "ROLE", "role", + "BDAY", "bday", + "DESC", "desc", + "N/GIVEN", "n-given", + "N/FAMILY", "n-family", + "ADR/STREET", "adr-street", + "ADR/EXTADD", "adr-extadd", + "ADR/LOCALITY", "adr-locality", + "ADR/REGION", "adr-region", + "ADR/PCODE", "adr-pcode", + "ADR/CTRY", "adr-country", + "ORG/ORGNAME", "org-orgname", + "ORG/ORGUNIT", "org-orgunit", + NULL, NULL +}; + +static os_t _iq_vcard_to_object(pkt_t pkt) { + os_t os; + os_object_t o; + int i = 0, elem; + char *vkey, *dkey, *vskey, ekey[10], cdata[4096]; + + log_debug(ZONE, "building object from packet"); + + os = os_new(); + o = os_object_new(os); + + while(_iq_vcard_map[i] != NULL) { + vkey = _iq_vcard_map[i]; + dkey = _iq_vcard_map[i + 1]; + + i += 2; + + vskey = strchr(vkey, '/'); + if(vskey == NULL) { + vskey = vkey; + elem = 2; + } else { + sprintf(ekey, "%.*s", vskey - vkey, vkey); + elem = nad_find_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 2), ekey, 1); + if(elem < 0) + continue; + vskey++; + } + + elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, 2), vskey, 1); + if(elem < 0 || NAD_CDATA_L(pkt->nad, elem) == 0) + continue; + + log_debug(ZONE, "extracted vcard key %s val '%.*s' for db key %s", vkey, NAD_CDATA_L(pkt->nad, elem), NAD_CDATA(pkt->nad, elem), dkey); + + snprintf(cdata, 4096, "%.*s", NAD_CDATA_L(pkt->nad, elem), NAD_CDATA(pkt->nad, elem)); + cdata[4095] = '\0'; + os_object_put(o, dkey, cdata, os_type_STRING); + } + + return os; +} + +static pkt_t _iq_vcard_to_pkt(sm_t sm, os_t os) { + pkt_t pkt; + os_object_t o; + int i = 0, elem; + char *vkey, *dkey, *vskey, ekey[10], *dval; + + log_debug(ZONE, "building packet from object"); + + pkt = pkt_create(sm, "iq", "result", NULL, NULL); + nad_append_elem(pkt->nad, nad_add_namespace(pkt->nad, uri_VCARD, NULL), "vCard", 2); + + if(!os_iter_first(os)) + return pkt; + o = os_iter_object(os); + + while(_iq_vcard_map[i] != NULL) { + vkey = _iq_vcard_map[i]; + dkey = _iq_vcard_map[i + 1]; + + i += 2; + + if(!os_object_get_str(os, o, dkey, &dval)) + continue; + + vskey = strchr(vkey, '/'); + if(vskey == NULL) { + vskey = vkey; + elem = 2; + } else { + sprintf(ekey, "%.*s", vskey - vkey, vkey); + elem = nad_find_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 2), ekey, 1); + if(elem < 0) + elem = nad_append_elem(pkt->nad, NAD_ENS(pkt->nad, 2), ekey, 3); + vskey++; + } + + log_debug(ZONE, "extracted dbkey %s val '%s' for vcard key %s", dkey, dval, vkey); + + nad_append_elem(pkt->nad, NAD_ENS(pkt->nad, 2), vskey, pkt->nad->elems[elem].depth + 1); + nad_append_cdata(pkt->nad, dval, strlen(dval), pkt->nad->elems[elem].depth + 2); + } + + return pkt; +} + +static mod_ret_t _iq_vcard_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) { + os_t os; + st_ret_t ret; + pkt_t result; + + /* only handle vcard sets and gets that aren't to anyone */ + if(pkt->to != NULL || (pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET) || pkt->ns != ns_VCARD) + return mod_PASS; + + /* get */ + if(pkt->type == pkt_IQ) { + ret = storage_get(sess->user->sm->st, "vcard", jid_user(sess->jid), NULL, &os); + switch(ret) { + case st_FAILED: + return -stanza_err_INTERNAL_SERVER_ERROR; + + case st_NOTIMPL: + return -stanza_err_FEATURE_NOT_IMPLEMENTED; + + case st_NOTFOUND: + nad_set_attr(pkt->nad, 1, -1, "type", "result", 6); + nad_set_attr(pkt->nad, 1, -1, "to", NULL, 0); + nad_set_attr(pkt->nad, 1, -1, "from", NULL, 0); + + pkt_sess(pkt, sess); + + return mod_HANDLED; + + case st_SUCCESS: + result = _iq_vcard_to_pkt(sess->user->sm, os); + os_free(os); + + nad_set_attr(result->nad, 1, -1, "type", "result", 6); + pkt_id(pkt, result); + + pkt_sess(result, sess); + + pkt_free(pkt); + + return mod_HANDLED; + } + + /* we never get here */ + pkt_free(pkt); + return mod_HANDLED; + } + + os = _iq_vcard_to_object(pkt); + ret = storage_replace(sess->user->sm->st, "vcard", jid_user(sess->jid), NULL, os); + os_free(os); + + switch(ret) { + case st_FAILED: + return -stanza_err_INTERNAL_SERVER_ERROR; + + case st_NOTIMPL: + return -stanza_err_FEATURE_NOT_IMPLEMENTED; + + default: + result = pkt_create(sess->user->sm, "iq", "result", NULL, NULL); + + pkt_id(pkt, result); + + pkt_sess(result, sess); + + pkt_free(pkt); + + return mod_HANDLED; + } + + /* we never get here */ + pkt_free(pkt); + return mod_HANDLED; +} + +static mod_ret_t _iq_vcard_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) { + os_t os; + st_ret_t ret; + pkt_t result; + + /* only handle vcard sets and gets */ + if((pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET) || pkt->ns != ns_VCARD) + return mod_PASS; + + /* error them if they're trying to do a set */ + if(pkt->type == pkt_IQ_SET) + return -stanza_err_FORBIDDEN; + + ret = storage_get(user->sm->st, "vcard", jid_user(user->jid), NULL, &os); + switch(ret) { + case st_FAILED: + return -stanza_err_INTERNAL_SERVER_ERROR; + + case st_NOTIMPL: + return -stanza_err_FEATURE_NOT_IMPLEMENTED; + + case st_NOTFOUND: + return -stanza_err_ITEM_NOT_FOUND; + + case st_SUCCESS: + result = _iq_vcard_to_pkt(user->sm, os); + os_free(os); + + result->to = jid_dup(pkt->from); + result->from = jid_dup(pkt->to); + + nad_set_attr(result->nad, 1, -1, "to", jid_full(result->to), 0); + nad_set_attr(result->nad, 1, -1, "from", jid_full(result->from), 0); + + pkt_id(pkt, result); + + pkt_router(result); + + pkt_free(pkt); + + return mod_HANDLED; + } + + /* we never get here */ + pkt_free(pkt); + return mod_HANDLED; +} + +static void _iq_vcard_user_delete(mod_instance_t mi, jid_t jid) { + log_debug(ZONE, "deleting vcard for %s", jid_user(jid)); + + storage_delete(mi->sm->st, "vcard", jid_user(jid), NULL); +} + +static void _iq_vcard_free(module_t mod) { + sm_unregister_ns(mod->mm->sm, uri_VCARD); + feature_unregister(mod->mm->sm, uri_VCARD); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->in_sess = _iq_vcard_in_sess; + mod->pkt_user = _iq_vcard_pkt_user; + mod->user_delete = _iq_vcard_user_delete; + mod->free = _iq_vcard_free; + + ns_VCARD = sm_register_ns(mod->mm->sm, uri_VCARD); + feature_register(mod->mm->sm, uri_VCARD); + + return 0; +} diff --git a/sm/mod_iq_version.c b/sm/mod_iq_version.c new file mode 100644 index 00000000..6ed58109 --- /dev/null +++ b/sm/mod_iq_version.c @@ -0,0 +1,220 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_iq_version.c + * @brief software version + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.16 $ + */ + +#ifdef HAVE_SYS_UTSNAME_H +# include +#endif + +#define uri_VERSION "jabber:iq:version" +static int ns_VERSION = 0; + +static mod_ret_t _iq_version_pkt_sm(mod_instance_t mi, pkt_t pkt) { + char buf[256]; + +#if defined(HAVE_UNAME) + struct utsname un; + +#elif defined(WIN32) + char sysname[64]; + char release[64]; + char version[64]; + + OSVERSIONINFOEX osvi; + BOOL bOsVersionInfoEx; + BOOL bSomeError = FALSE; + + sysname[0] = 0; + release[0] = 0; + version[0] = 0; +#endif + + /* we only want to play with iq:version gets */ + if(pkt->type != pkt_IQ || pkt->ns != ns_VERSION) + return mod_PASS; + + nad_insert_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 1), "name", "session manager (jabberd)"); + nad_insert_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 1), "version", mi->sm->signature); + + /* figure out the os type */ +#if defined(HAVE_UNAME) + uname(&un); + snprintf(buf, 256, "%s %s", un.sysname, un.release); + nad_insert_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 1), "os", buf); + +#elif defined(WIN32) + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) ) + { + /* If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. */ + + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) + { + snprintf(sysname, 64, "unknown"); + bSomeError = TRUE; + } + } + if (!bSomeError) + { + switch (osvi.dwPlatformId) + { + case VER_PLATFORM_WIN32_NT: + /* Test for the product. */ + if ( osvi.dwMajorVersion <= 4 ) + snprintf(sysname, 64, "Microsoft Windows NT"); + + if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 ) + snprintf(sysname, 64, "Microsoft Windows 2000"); + + if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 ) + snprintf(sysname, 64, "Microsoft Windows XP"); + + /* Test for product type. */ + + if( bOsVersionInfoEx ) + { + if ( osvi.wProductType == VER_NT_WORKSTATION ) + { + if( osvi.wSuiteMask & VER_SUITE_PERSONAL ) + snprintf(release, 64, "Personal" ); + else + snprintf(release, 64, "Professional" ); + } + + else if ( osvi.wProductType == VER_NT_SERVER ) + { + if( osvi.wSuiteMask & VER_SUITE_DATACENTER ) + snprintf(release, 64, "DataCenter Server" ); + else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE ) + snprintf(release, 64, "Advanced Server" ); + else + snprintf(release, 64, "Server" ); + } + } + else + { + HKEY hKey; + char szProductType[80]; + DWORD dwBufLen; + + RegOpenKeyEx( HKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Control\\ProductOptions", + 0, KEY_QUERY_VALUE, &hKey ); + RegQueryValueEx( hKey, "ProductType", NULL, NULL, + (LPBYTE) szProductType, &dwBufLen); + RegCloseKey( hKey ); + if ( lstrcmpi( "WINNT", szProductType) == 0 ) + snprintf(release, 64, "Professional" ); + if ( lstrcmpi( "LANMANNT", szProductType) == 0 ) + snprintf(release, 64, "Server" ); + if ( lstrcmpi( "SERVERNT", szProductType) == 0 ) + snprintf(release, 64, "Advanced Server" ); + } + + /* Display version, service pack (if any), and build number. */ + + if ( osvi.dwMajorVersion <= 4 ) + { + snprintf(version, 64, "version %d.%d %s (Build %d)", + osvi.dwMajorVersion, + osvi.dwMinorVersion, + osvi.szCSDVersion, + osvi.dwBuildNumber & 0xFFFF); + } + else + { + snprintf(version, 64, "%s (Build %d)", + osvi.szCSDVersion, + osvi.dwBuildNumber & 0xFFFF); + } + break; + + case VER_PLATFORM_WIN32_WINDOWS: + + if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) + { + snprintf(sysname, 64, "Microsoft Windows 95"); + if ( osvi.szCSDVersion[1] == 'C' || osvi.szCSDVersion[1] == 'B' ) + snprintf(release, 64, "OSR2" ); + } + + if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10) + { + snprintf(sysname, 64, "Microsoft Windows 98"); + if ( osvi.szCSDVersion[1] == 'A' ) + snprintf(release, 64, "SE" ); + } + + if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90) + { + snprintf(sysname, 64, "Microsoft Windows Me"); + } + break; + + case VER_PLATFORM_WIN32s: + + snprintf(sysname, 64, "Microsoft Win32s"); + break; + } + } + + snprintf(buf, 256, "%s %s %s", sysname, release, version); + buf[256] = '\0'; + nad_insert_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 1), "os", buf); + +#else + nad_insert_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 1), "os", "unknown"); +#endif + + /* tell them */ + nad_set_attr(pkt->nad, 1, -1, "type", "result", 6); + pkt_router(pkt_tofrom(pkt)); + + return mod_HANDLED; +} + +static void _iq_version_free(module_t mod) { + sm_unregister_ns(mod->mm->sm, uri_VERSION); + feature_unregister(mod->mm->sm, uri_VERSION); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->pkt_sm = _iq_version_pkt_sm; + mod->free = _iq_version_free; + + ns_VERSION = sm_register_ns(mod->mm->sm, uri_VERSION); + feature_register(mod->mm->sm, uri_VERSION); + + return 0; +} diff --git a/sm/mod_offline.c b/sm/mod_offline.c new file mode 100644 index 00000000..cb1f9f03 --- /dev/null +++ b/sm/mod_offline.c @@ -0,0 +1,233 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_offline.c + * @brief offline storage + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.26 $ + */ + +static mod_ret_t _offline_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) { + st_ret_t ret; + os_t os; + os_object_t o; + nad_t nad; + pkt_t queued; + int ns, elem, attr; + char cttl[15], cstamp[18]; + time_t ttl, stamp; + + /* if they're becoming available for the first time */ + if(pkt->type == pkt_PRESENCE && pkt->to == NULL && sess->user->top == NULL) { + + ret = storage_get(pkt->sm->st, "queue", jid_user(sess->jid), NULL, &os); + if(ret != st_SUCCESS) { + log_debug(ZONE, "storage_get returned %d", ret); + return mod_PASS; + } + + if(os_iter_first(os)) + do { + o = os_iter_object(os); + + if(os_object_get_nad(os, o, "xml", &nad)) { + queued = pkt_new(pkt->sm, nad_copy(nad)); + if(queued == NULL) { + log_debug(ZONE, "invalid queued packet, not delivering"); + } else { + /* check expiry as necessary */ + if((ns = nad_find_scoped_namespace(queued->nad, uri_EXPIRE, NULL)) >= 0 && + (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 && + (attr = nad_find_attr(queued->nad, elem, -1, "seconds", NULL)) >= 0) { + snprintf(cttl, 15, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr)); + ttl = atoi(cttl); + + /* it should have a x:delay stamp, because we stamp everything we store */ + if((ns = nad_find_scoped_namespace(queued->nad, uri_DELAY, NULL)) >= 0 && + (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 && + (attr = nad_find_attr(queued->nad, elem, -1, "stamp", NULL)) >= 0) { + snprintf(cstamp, 18, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr)); + stamp = datetime_in(cstamp); + + if(stamp + ttl <= time(NULL)) { + log_debug(ZONE, "queued packet has expired, dropping"); + pkt_free(queued); + continue; + } + } + } + + log_debug(ZONE, "delivering queued packet to %s", jid_full(sess->jid)); + pkt_sess(queued, sess); + } + } + } while(os_iter_next(os)); + + os_free(os); + + /* drop the spool */ + storage_delete(pkt->sm->st, "queue", jid_user(sess->jid), NULL); + } + + /* pass it so that other modules and mod_presence can get it */ + return mod_PASS; +} + +static mod_ret_t _offline_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) { + int ns, elem, attr; + os_t os; + os_object_t o; + pkt_t event; + + /* send messages and s10ns to the top session */ + if(user->top != NULL && (pkt->type & pkt_MESSAGE || pkt->type & pkt_S10N)) { + pkt_sess(pkt, user->top); + return mod_HANDLED; + } + + /* save messages and s10ns for later */ + if(pkt->type & pkt_MESSAGE || pkt->type & pkt_S10N) { + log_debug(ZONE, "saving message for later"); + + pkt_delay(pkt, time(NULL), user->sm->id); + + /* new object */ + os = os_new(); + o = os_object_new(os); + + os_object_put(o, "xml", pkt->nad, os_type_NAD); + + /* store it */ + switch(storage_put(user->sm->st, "queue", jid_user(user->jid), os)) { + case st_FAILED: + os_free(os); + return -stanza_err_INTERNAL_SERVER_ERROR; + + case st_NOTIMPL: + os_free(os); + return -stanza_err_SERVICE_UNAVAILABLE; /* xmpp-im 9.5#4 */ + + default: + os_free(os); + + /* send offline events if they asked for it */ + /* if there's an id element, then this is a notification, not a request, so ignore it */ + + if((ns = nad_find_scoped_namespace(pkt->nad, uri_EVENT, NULL)) >= 0 && + (elem = nad_find_elem(pkt->nad, 1, ns, "x", 1)) >= 0 && + nad_find_elem(pkt->nad, elem, ns, "offline", 1) >= 0 && + nad_find_elem(pkt->nad, elem, ns, "id", 1) < 0) { + + event = pkt_create(user->sm, "message", NULL, jid_full(pkt->from), jid_full(pkt->to)); + + attr = nad_find_attr(pkt->nad, 1, -1, "type", NULL); + if(attr >= 0) + nad_set_attr(event->nad, 1, -1, "type", NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + + ns = nad_add_namespace(event->nad, uri_EVENT, NULL); + nad_append_elem(event->nad, ns, "x", 2); + nad_append_elem(event->nad, ns, "offline", 3); + + nad_append_elem(event->nad, ns, "id", 3); + attr = nad_find_attr(pkt->nad, 1, -1, "id", NULL); + if(attr >= 0) + nad_append_cdata(event->nad, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr), 4); + + pkt_router(event); + } + + pkt_free(pkt); + return mod_HANDLED; + } + } + + return mod_PASS; +} + +static void _offline_user_delete(mod_instance_t mi, jid_t jid) { + os_t os; + os_object_t o; + nad_t nad; + pkt_t queued; + int ns, elem, attr; + char cttl[15], cstamp[18]; + time_t ttl, stamp; + + log_debug(ZONE, "deleting queue for %s", jid_user(jid)); + + /* bounce the queue */ + if(storage_get(mi->mod->mm->sm->st, "queue", jid_user(jid), NULL, &os) == st_SUCCESS) { + if(os_iter_first(os)) + do { + o = os_iter_object(os); + + if(os_object_get_nad(os, o, "xml", &nad)) { + queued = pkt_new(mi->mod->mm->sm, nad); + if(queued == NULL) { + log_debug(ZONE, "invalid queued packet, not delivering"); + } else { + /* check expiry as necessary */ + if((ns = nad_find_scoped_namespace(queued->nad, uri_EXPIRE, NULL)) >= 0 && + (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 && + (attr = nad_find_attr(queued->nad, elem, -1, "seconds", NULL)) >= 0) { + snprintf(cttl, 15, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr)); + ttl = atoi(cttl); + + /* it should have a x:delay stamp, because we stamp everything we store */ + if((ns = nad_find_scoped_namespace(queued->nad, uri_DELAY, NULL)) >= 0 && + (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 && + (attr = nad_find_attr(queued->nad, elem, -1, "stamp", NULL)) >= 0) { + snprintf(cstamp, 18, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr)); + stamp = datetime_in(cstamp); + + if(stamp + ttl <= time(NULL)) { + log_debug(ZONE, "queued packet has expired, dropping"); + pkt_free(queued); + continue; + } + } + } + + log_debug(ZONE, "bouncing queued packet from %s", jid_full(queued->from)); + pkt_router(pkt_error(queued, stanza_err_ITEM_NOT_FOUND)); + } + } + } while(os_iter_next(os)); + + os_free(os); + } + + storage_delete(mi->sm->st, "queue", jid_user(jid), NULL); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if (mod->init) return 0; + + mod->in_sess = _offline_in_sess; + mod->pkt_user = _offline_pkt_user; + mod->user_delete = _offline_user_delete; + + return 0; +} diff --git a/sm/mod_presence.c b/sm/mod_presence.c new file mode 100644 index 00000000..4084886f --- /dev/null +++ b/sm/mod_presence.c @@ -0,0 +1,151 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_presence.c + * @brief presence tracker driver + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.17 $ + */ + +/** presence from the session */ +static mod_ret_t _presence_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) { + /* only handle presence */ + if(!(pkt->type & pkt_PRESENCE)) + return mod_PASS; + + /* reset from if necessary */ + if(pkt->from == NULL || jid_compare_user(pkt->from, sess->jid) != 0) { + if(pkt->from != NULL) + jid_free(pkt->from); + + pkt->from = jid_dup(sess->jid); + nad_set_attr(pkt->nad, 1, -1, "from", jid_full(pkt->from), 0); + } + + /* presence broadcast (T1, T2, T3) */ + if(pkt->to == NULL) + pres_update(sess, pkt); + + /* directed presence (T7, T8) */ + else + pres_deliver(sess, pkt); + + return mod_HANDLED; +} + +/* drop incoming presence if the user isn't around, + * so we don't have to load them during broadcasts */ +mod_ret_t _presence_in_router(mod_instance_t mi, pkt_t pkt) { + user_t user; + sess_t sess; + + /* only check presence */ + if(!(pkt->type & pkt_PRESENCE)) + return mod_PASS; + + /* get the user _without_ doing a load */ + user = xhash_get(mi->mod->mm->sm->users, jid_user(pkt->to)); + + /* no user, or no sessions, bail */ + if(user == NULL || user->sessions == NULL) { + pkt_free(pkt); + return mod_HANDLED; + } + + /* only pass if there's at least one available session */ + for(sess = user->sessions; sess != NULL; sess = sess->next) + if(sess->available && sess->pri >= 0) + return mod_PASS; + + /* no available sessions, drop */ + pkt_free(pkt); + + return mod_HANDLED; +} + +/** presence to a user */ +static mod_ret_t _presence_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) { + sess_t sess; + + /* only handle presence */ + if(!(pkt->type & pkt_PRESENCE)) + return mod_PASS; + + /* errors get tracked, but still delivered (T6) */ + if(pkt->type & pkt_ERROR) { + /* find the session */ + sess = sess_match(user, pkt->to->resource); + if(sess == NULL) { + log_debug(ZONE, "bounced presence, but no corresponding session anymore, dropping"); + pkt_free(pkt); + return mod_HANDLED; + } + + log_debug(ZONE, "bounced presence, tracking"); + pres_error(sess, pkt->from); + + /* bounced probes get dropped */ + if((pkt->type & pkt_PRESENCE_PROBE) == pkt_PRESENCE_PROBE) { + pkt_free(pkt); + return mod_HANDLED; + } + } + + /* someone sent us a raw invisible? hrm */ + if(pkt->type == pkt_PRESENCE_INVIS) { + log_debug(ZONE, "urgh, broken server sent us an invisible, rewriting it"); + nad_set_attr(pkt->nad, 1, -1, "type", "unavailable", 11); + pkt->type = pkt_PRESENCE_UN; + } + + /* if there's a resource, send it direct */ + if(*pkt->to->resource != '\0') { + sess = sess_match(user, pkt->to->resource); + if(sess == NULL) + /* this resource isn't online */ + return -stanza_err_RECIPIENT_UNAVAILABLE; /* xmpp-im-11 9.5#2 */ + + pkt_sess(pkt, sess); + return mod_HANDLED; + } + + /* remote presence updates (T4, T5) */ + pres_in(user, pkt); + + return mod_HANDLED; +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->in_sess = _presence_in_sess; + mod->in_router = _presence_in_router; + mod->pkt_user = _presence_pkt_user; + + feature_register(mod->mm->sm, "presence"); + feature_register(mod->mm->sm, "presence-invisible"); + + return 0; +} diff --git a/sm/mod_privacy.c b/sm/mod_privacy.c new file mode 100644 index 00000000..794ffb2a --- /dev/null +++ b/sm/mod_privacy.c @@ -0,0 +1,1059 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_privacy.c + * @brief privacy lists + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.32 $ + */ + +#define uri_PRIVACY "jabber:iq:privacy" +static int ns_PRIVACY = 0; + +typedef struct zebra_st *zebra_t; +typedef struct zebra_list_st *zebra_list_t; +typedef struct zebra_item_st *zebra_item_t; + +typedef enum { + zebra_NONE, + zebra_JID, + zebra_GROUP, + zebra_S10N +} zebra_item_type_t; + +typedef enum { + block_NONE = 0x00, + block_MESSAGE = 0x01, + block_PRES_IN = 0x02, + block_PRES_OUT = 0x04, + block_IQ = 0x08 +} zebra_block_type_t; + +/** zebra data for a single user */ +struct zebra_st { + xht lists; + + zebra_list_t def; +}; + +struct zebra_list_st { + pool p; + + char *name; + + zebra_item_t items, last; +}; + +struct zebra_item_st { + zebra_item_type_t type; + + jid_t jid; + + char *group; + + int to; + int from; + + int deny; /* 0 = allow, 1 = deny */ + + int order; + + zebra_block_type_t block; + + zebra_item_t next, prev; +}; + +/* union for xhash_iter_get to comply with strict-alias rules for gcc3 */ +union xhashv +{ + void **val; + zebra_list_t *z_val; +}; + +static void _privacy_free_z(zebra_t z) { + zebra_list_t zlist; + union xhashv xhv; + + log_debug(ZONE, "freeing zebra ctx"); + + if(xhash_iter_first(z->lists)) + do { + xhv.z_val = &zlist; + xhash_iter_get(z->lists, NULL, xhv.val); + pool_free(zlist->p); + } while(xhash_iter_next(z->lists)); + + xhash_free(z->lists); + free(z); +} + +static void _privacy_user_free(zebra_t *z) { + if(*z != NULL) + _privacy_free_z(*z); +} + +static int _privacy_user_load(mod_instance_t mi, user_t user) { + module_t mod = mi->mod; + zebra_t z; + os_t os; + os_object_t o; + zebra_list_t zlist; + pool p; + zebra_item_t zitem, scan; + char *str; + + log_debug(ZONE, "loading privacy lists for %s", jid_user(user->jid)); + + /* free if necessary */ + z = user->module_data[mod->index]; + if(z != NULL) + _privacy_free_z(z); + + z = (zebra_t) malloc(sizeof(struct zebra_st)); + memset(z, 0, sizeof(struct zebra_st)); + + z->lists = xhash_new(101); + + user->module_data[mod->index] = z; + + pool_cleanup(user->p, (void (*))(void *) _privacy_user_free, &(user->module_data[mod->index])); + + /* pull the whole lot */ + if(storage_get(user->sm->st, "privacy-items", jid_user(user->jid), NULL, &os) == st_SUCCESS) { + if(os_iter_first(os)) + do { + o = os_iter_object(os); + + /* list name */ + if(!os_object_get_str(os, o, "list", &str)) { + log_debug(ZONE, "item with no list field, skipping"); + continue; + } + + log_debug(ZONE, "got item for list %s", str); + + zlist = xhash_get(z->lists, str); + + /* new list */ + if(zlist == NULL) { + log_debug(ZONE, "creating list %s", str); + + p = pool_new(); + + zlist = (zebra_list_t) pmalloco(p, sizeof(struct zebra_list_st)); + + zlist->p = p; + zlist->name = pstrdup(p, str); + + xhash_put(z->lists, zlist->name, (void *) zlist); + } + + /* new item */ + zitem = (zebra_item_t) pmalloco(zlist->p, sizeof(struct zebra_item_st)); + + /* item type */ + if(os_object_get_str(os, o, "type", &str)) + switch(str[0]) { + case 'j': + zitem->type = zebra_JID; + break; + + case 'g': + zitem->type = zebra_GROUP; + break; + + case 's': + zitem->type = zebra_S10N; + break; + } + + /* item value, according to type */ + if(zitem->type != zebra_NONE) { + if(!os_object_get_str(os, o, "value", &str)) { + log_debug(ZONE, "no value on non-fall-through item, dropping this item"); + free(zitem); + continue; + } + + switch(zitem->type) { + + case zebra_JID: + zitem->jid = jid_new(user->sm->pc, str, strlen(str)); + if(zitem->jid == NULL) { + log_debug(ZONE, "invalid jid '%s' on item, dropping this item", str); + free(zitem); + continue; + } + + pool_cleanup(zlist->p, free, zitem->jid); + + log_debug(ZONE, "jid item with value '%s'", jid_full(zitem->jid)); + + break; + + case zebra_GROUP: + zitem->group = pstrdup(zlist->p, str); + + log_debug(ZONE, "group item with value '%s'", zitem->group); + + break; + + case zebra_S10N: + if(strcmp(str, "to") == 0) + zitem->to = 1; + else if(strcmp(str, "from") == 0) + zitem->from = 1; + else if(strcmp(str, "both") == 0) + zitem->to = zitem->from = 1; + else if(strcmp(str, "none") != 0) { + log_debug(ZONE, "invalid value '%s' on s10n item, dropping this item", str); + free(zitem); + continue; + } + + log_debug(ZONE, "s10n item with value '%s' (to %d from %d)", str, zitem->to, zitem->from); + + break; + + case zebra_NONE: + /* can't get here */ + break; + } + } + + /* action */ + os_object_get_bool(os, o, "deny", &zitem->deny); + if(zitem->deny) { + log_debug(ZONE, "deny rule"); + } else { + log_debug(ZONE, "accept rule"); + } + + os_object_get_int(os, o, "order", &(zitem->order)); + log_debug(ZONE, "order %d", zitem->order); + + os_object_get_int(os, o, "block", (int *) &(zitem->block)); + log_debug(ZONE, "block 0x%x", zitem->block); + + /* insert it */ + for(scan = zlist->items; scan != NULL; scan = scan->next) + if(zitem->order < scan->order) + break; + + /* we're >= everyone, add us to the end */ + if(scan == NULL) { + if(zlist->last == NULL) + zlist->items = zlist->last = zitem; + else { + zlist->last->next = zitem; + zitem->prev = zlist->last; + zlist->last = zitem; + } + } + + /* insert just before scan */ + else { + if(zlist->items == scan) { + zitem->next = zlist->items; + zlist->items = zitem; + scan->prev = zitem; + } else { + zitem->next = scan; + zitem->prev = scan->prev; + scan->prev->next = zitem; + scan->prev = zitem; + } + } + } while(os_iter_next(os)); + + os_free(os); + } + + /* default list */ + if(storage_get(user->sm->st, "privacy-default", jid_user(user->jid), NULL, &os) == st_SUCCESS) { + if(os_iter_first(os)) + do { + o = os_iter_object(os); + + if(os_object_get_str(os, o, "default", &str)) { + z->def = (zebra_list_t) xhash_get(z->lists, str); + if(z->def == NULL) { + log_debug(ZONE, "storage says the default list for %s is %s, but it doesn't exist!", jid_user(user->jid), str); + } else { + log_debug(ZONE, "user %s has default list %s", jid_user(user->jid), str); + } + } + } while(os_iter_next(os)); + + os_free(os); + } + + return 0; +} + +/** returns 0 if the packet should be allowed, otherwise 1 */ +static int _privacy_action(user_t user, zebra_list_t zlist, jid_t jid, pkt_type_t ptype, int in) { + zebra_item_t scan; + int match, i; + item_t ritem; + unsigned char domres[2048]; + + log_debug(ZONE, "running match on list %s for %s (packet type 0x%x) (%s)", zlist->name, jid_full(jid), ptype, in ? "incoming" : "outgoing"); + + /* loop over the list, trying to find a match */ + for(scan = zlist->items; scan != NULL; scan = scan->next) { + match = 0; + + switch(scan->type) { + case zebra_NONE: + /* fall through, all packets match this */ + match = 1; + break; + + case zebra_JID: + sprintf(domres, "%s/%s", jid->domain, jid->resource); + + /* jid check - match node@dom/res, then node@dom, then dom/resource, then dom */ + if(jid_compare_full(scan->jid, jid) == 0 || + strcmp(jid_full(scan->jid), jid_user(jid)) == 0 || + strcmp(jid_full(scan->jid), domres) == 0 || + strcmp(jid_full(scan->jid), jid->domain) == 0) + match = 1; + + break; + + case zebra_GROUP: + /* roster group check - get the roster item, node@dom/res, then node@dom, then dom */ + ritem = xhash_get(user->roster, jid_full(jid)); + if(ritem == NULL) ritem = xhash_get(user->roster, jid_user(jid)); + if(ritem == NULL) ritem = xhash_get(user->roster, jid->domain); + + /* got it, do the check */ + if(ritem != NULL) + for(i = 0; i < ritem->ngroups; i++) + if(strcmp(scan->group, ritem->groups[i]) == 0) + match = 1; + + break; + + case zebra_S10N: + /* roster item check - get the roster item, node@dom/res, then node@dom, then dom */ + ritem = xhash_get(user->roster, jid_full(jid)); + if(ritem == NULL) ritem = xhash_get(user->roster, jid_user(jid)); + if(ritem == NULL) ritem = xhash_get(user->roster, jid->domain); + + /* got it, do the check */ + if(ritem != NULL) + if(scan->to == ritem->to && scan->from == ritem->from) + match = 1; + + break; + } + + /* if we matched a rule, we have to do packet block matching */ + if(match) { + /* no packet blocking, matching done */ + if(scan->block == block_NONE) + return scan->deny; + + /* incoming checks block_MESSAGE, block_PRES_IN and block_IQ */ + if(in) { + if(ptype & pkt_MESSAGE && scan->block & block_MESSAGE) + return scan->deny; + if(ptype & pkt_PRESENCE && scan->block & block_PRES_IN) + return scan->deny; + if(ptype & pkt_IQ && scan->block & block_IQ) + return scan->deny; + } else if(ptype & pkt_PRESENCE && scan->block & block_PRES_OUT && ptype != pkt_PRESENCE_PROBE) { + /* outgoing check, just block_PRES_OUT */ + return scan->deny; + } + } + } + + /* didn't match the list, so allow */ + return 0; +} + +/** check incoming packets */ +static mod_ret_t _privacy_in_router(mod_instance_t mi, pkt_t pkt) { + module_t mod = mi->mod; + user_t user; + zebra_t z; + sess_t sess = NULL; + zebra_list_t zlist = NULL; + + /* if its coming to the sm, let it in */ + if(pkt->to == NULL || pkt->to->node[0] == '\0') + return mod_PASS; + + /* get the user */ + user = user_load(mod->mm->sm, pkt->to); + if(user == NULL) { + log_debug(ZONE, "no user %s, passing packet", jid_user(pkt->to)); + return mod_PASS; + } + + /* get our lists */ + z = (zebra_t) user->module_data[mod->index]; + + /* find a session */ + if(*pkt->to->resource != '\0') + sess = sess_match(user, pkt->to->resource); + + /* didn't match a session, so use the top session */ + if(sess == NULL) + sess = user->top; + + /* get the active list for the session */ + if(sess != NULL) + zlist = (zebra_list_t) sess->module_data[mod->index]; + + /* no active list, so use the default list */ + if(zlist == NULL) + zlist = z->def; + + /* no list, so allow everything */ + if(zlist == NULL) + return mod_PASS; + + /* figure out the action */ + if(_privacy_action(user, zlist, pkt->from, pkt->type, 1) == 0) + return mod_PASS; + + /* deny */ + log_debug(ZONE, "denying incoming packet based on privacy policy"); + + /* iqs get special treatment */ + if(pkt->type == pkt_IQ || pkt->type == pkt_IQ_SET) + return -stanza_err_FEATURE_NOT_IMPLEMENTED; + + /* drop it */ + pkt_free(pkt); + return mod_HANDLED; +} + +/** check outgoing packets */ +static mod_ret_t _privacy_out_router(mod_instance_t mi, pkt_t pkt) { + module_t mod = mi->mod; + user_t user; + zebra_t z; + sess_t sess = NULL; + zebra_list_t zlist = NULL; + + /* if its coming from the sm, let it go */ + if(pkt->from == NULL || pkt->from->node[0] == '\0') + return mod_PASS; + + /* get the user */ + user = user_load(mod->mm->sm, pkt->from); + if(user == NULL) { + log_debug(ZONE, "no user %s, passing packet", jid_user(pkt->to)); + return mod_PASS; + } + + /* get our lists */ + z = (zebra_t) user->module_data[mod->index]; + + /* find a session */ + if(*pkt->from->resource != '\0') + sess = sess_match(user, pkt->from->resource); + + /* get the active list for the session */ + if(sess != NULL) + zlist = (zebra_list_t) sess->module_data[mod->index]; + + /* no active list, so use the default list */ + if(zlist == NULL) + zlist = z->def; + + /* no list, so allow everything */ + if(zlist == NULL) + return mod_PASS; + + /* figure out the action */ + if(_privacy_action(user, zlist, pkt->to, pkt->type, 0) == 0) + return mod_PASS; + + /* deny */ + log_debug(ZONE, "denying outgoing packet based on privacy policy"); + + /* drop it */ + pkt_free(pkt); + return mod_HANDLED; +} + +/** add a list to the return packet */ +static void _privacy_result_builder(xht zhash, const char *name, void *val, void *arg) { + zebra_list_t zlist = (zebra_list_t) val; + pkt_t pkt = (pkt_t) arg; + int ns, query, list, item; + zebra_item_t zitem; + char order[14]; + + ns = nad_find_scoped_namespace(pkt->nad, uri_PRIVACY, NULL); + query = nad_find_elem(pkt->nad, 1, ns, "query", 1); + + list = nad_insert_elem(pkt->nad, query, ns, "list", NULL); + nad_set_attr(pkt->nad, list, -1, "name", zlist->name, 0); + + /* run through the items and build the nad */ + for(zitem = zlist->items; zitem != NULL; zitem = zitem->next) { + item = nad_insert_elem(pkt->nad, list, ns, "item", NULL); + + switch(zitem->type) { + case zebra_JID: + nad_set_attr(pkt->nad, item, -1, "type", "jid", 0); + nad_set_attr(pkt->nad, item, -1, "value", jid_full(zitem->jid), 0); + break; + + case zebra_GROUP: + nad_set_attr(pkt->nad, item, -1, "type", "group", 0); + nad_set_attr(pkt->nad, item, -1, "value", zitem->group, 0); + break; + + case zebra_S10N: + nad_set_attr(pkt->nad, item, -1, "type", "subscription", 0); + + if(zitem->to == 1 && zitem->from == 1) + nad_set_attr(pkt->nad, item, -1, "value", "both", 4); + else if(zitem->to == 1) + nad_set_attr(pkt->nad, item, -1, "value", "to", 2); + else if(zitem->from == 1) + nad_set_attr(pkt->nad, item, -1, "value", "from", 4); + else + nad_set_attr(pkt->nad, item, -1, "value", "none", 4); + + break; + + case zebra_NONE: + break; + } + + if(zitem->deny) + nad_set_attr(pkt->nad, item, -1, "action", "deny", 4); + else + nad_set_attr(pkt->nad, item, -1, "action", "allow", 5); + + snprintf(order, 14, "%d", zitem->order); + order[13] = '\0'; + + nad_set_attr(pkt->nad, item, -1, "order", order, 0); + + if(zitem->block & block_MESSAGE) + nad_insert_elem(pkt->nad, item, ns, "message", NULL); + if(zitem->block & block_PRES_IN) + nad_insert_elem(pkt->nad, item, ns, "presence-in", NULL); + if(zitem->block & block_PRES_OUT) + nad_insert_elem(pkt->nad, item, ns, "presence-out", NULL); + if(zitem->block & block_IQ) + nad_insert_elem(pkt->nad, item, ns, "iq", NULL); + } +} + +/** add a list to the return packet */ +static void _privacy_lists_result_builder(xht zhash, const char *name, void *val, void *arg) { + zebra_list_t zlist = (zebra_list_t) val; + pkt_t pkt = (pkt_t) arg; + int ns, query, list; + + ns = nad_find_scoped_namespace(pkt->nad, uri_PRIVACY, NULL); + query = nad_find_elem(pkt->nad, 1, ns, "query", 1); + + list = nad_insert_elem(pkt->nad, query, ns, "list", NULL); + nad_set_attr(pkt->nad, list, -1, "name", zlist->name, 0); +} + +/** list management requests */ +static mod_ret_t _privacy_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) { + module_t mod = mi->mod; + int ns, query, list, name, active, def, item, type, value, action, order; + char corder[14], str[256], filter[1024]; + zebra_t z; + zebra_list_t zlist, old; + pool p; + zebra_item_t zitem, scan; + sess_t sscan; + pkt_t result; + os_t os; + os_object_t o; + st_ret_t ret; + + /* we only want to play with iq:privacy packets */ + if((pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET) || pkt->ns != ns_PRIVACY) + return mod_PASS; + + /* if it has a to, throw it out */ + if(pkt->to != NULL) + return -stanza_err_BAD_REQUEST; + + /* find the query */ + ns = nad_find_scoped_namespace(pkt->nad, uri_PRIVACY, NULL); + query = nad_find_elem(pkt->nad, 1, ns, "query", 1); + if(query < 0) + return -stanza_err_BAD_REQUEST; + + /* get our lists */ + z = (zebra_t) sess->user->module_data[mod->index]; + + /* update lists or set the active list */ + if(pkt->type == pkt_IQ_SET) { + /* find out what we're doing */ + list = nad_find_elem(pkt->nad, query, ns, "list", 1); + active = nad_find_elem(pkt->nad, query, ns, "active", 1); + def = nad_find_elem(pkt->nad, query, ns, "default", 1); + + /* we need something to do, but we can't do it all at once */ + if((list < 0 && active < 0 && def < 0) || (list >= 0 && (active >=0 || def >= 0))) + return -stanza_err_BAD_REQUEST; + + /* loop over any/all lists and store them */ + if(list >= 0) { + /* only allowed to change one list at a time */ + if(nad_find_elem(pkt->nad, list, ns, "list", 0) >= 0) { + /* hack the error in */ + pkt_error(pkt, stanza_err_BAD_REQUEST); + + pkt_sess(pkt, sess); + return mod_HANDLED; + } + + /* get the list name */ + name = nad_find_attr(pkt->nad, list, -1, "name", NULL); + if(name < 0) { + log_debug(ZONE, "no list name specified, failing request"); + return -stanza_err_BAD_REQUEST; + } + + snprintf(str, 256, "%.*s", NAD_AVAL_L(pkt->nad, name), NAD_AVAL(pkt->nad, name)); + str[255] = '\0'; + + log_debug(ZONE, "updating list %s", str); + + /* make a new one */ + p = pool_new(); + + zlist = (zebra_list_t) pmalloco(p, sizeof(struct zebra_list_st)); + + zlist->p = p; + zlist->name = pstrdup(p, str); + + os = os_new(); + + /* loop over the items */ + item = nad_find_elem(pkt->nad, list, ns, "item", 1); + while(item >= 0) { + /* extract things */ + type = nad_find_attr(pkt->nad, item, -1, "type", 0); + value = nad_find_attr(pkt->nad, item, -1, "value", 0); + action = nad_find_attr(pkt->nad, item, -1, "action", 0); + order = nad_find_attr(pkt->nad, item, -1, "order", 0); + + /* sanity */ + if(action < 0 || order < 0 || (type >= 0 && value < 0)) { + pool_free(p); + os_free(os); + return -stanza_err_BAD_REQUEST; + } + + /* new item */ + zitem = (zebra_item_t) pmalloco(p, sizeof(struct zebra_item_st)); + + /* have to store it too */ + o = os_object_new(os); + os_object_put(o, "list", zlist->name, os_type_STRING); + + /* type & value */ + if(type >= 0) { + if(NAD_AVAL_L(pkt->nad, type) == 3 && strncmp("jid", NAD_AVAL(pkt->nad, type), 3) == 0) { + zitem->type = zebra_JID; + + zitem->jid = jid_new(mod->mm->sm->pc, NAD_AVAL(pkt->nad, value), NAD_AVAL_L(pkt->nad, value)); + if(zitem->jid == NULL) { + log_debug(ZONE, "invalid jid '%.*s', failing request", NAD_AVAL_L(pkt->nad, value), NAD_AVAL(pkt->nad, value)); + pool_free(p); + os_free(os); + return -stanza_err_BAD_REQUEST; + } + + pool_cleanup(p, free, zitem->jid); + + log_debug(ZONE, "jid item with value '%s'", jid_full(zitem->jid)); + + os_object_put(o, "type", "jid", os_type_STRING); + os_object_put(o, "value", jid_full(zitem->jid), os_type_STRING); + } + + else if(NAD_AVAL_L(pkt->nad, type) == 5 && strncmp("group", NAD_AVAL(pkt->nad, type), 5) == 0) { + zitem->type = zebra_GROUP; + + zitem->group = pstrdupx(zlist->p, NAD_AVAL(pkt->nad, value), NAD_AVAL_L(pkt->nad, value)); + + /* !!! check if the group exists */ + + log_debug(ZONE, "group item with value '%s'", zitem->group); + + os_object_put(o, "type", "group", os_type_STRING); + os_object_put(o, "value", zitem->group, os_type_STRING); + } + + else if(NAD_AVAL_L(pkt->nad, type) == 12 && strncmp("subscription", NAD_AVAL(pkt->nad, type), 12) == 0) { + zitem->type = zebra_S10N; + + os_object_put(o, "type", "subscription", os_type_STRING); + + if(NAD_AVAL_L(pkt->nad, value) == 2 && strncmp("to", NAD_AVAL(pkt->nad, value), 2) == 0) { + zitem->to = 1; + os_object_put(o, "value", "to", os_type_STRING); + } else if(NAD_AVAL_L(pkt->nad, value) == 4 && strncmp("from", NAD_AVAL(pkt->nad, value), 4) == 0) { + zitem->from = 1; + os_object_put(o, "value", "from", os_type_STRING); + } else if(NAD_AVAL_L(pkt->nad, value) == 4 && strncmp("both", NAD_AVAL(pkt->nad, value), 4) == 0) { + zitem->to = zitem->from = 1; + os_object_put(o, "value", "both", os_type_STRING); + } else if(NAD_AVAL_L(pkt->nad, value) == 4 && strncmp("none", NAD_AVAL(pkt->nad, value), 4) == 0) + os_object_put(o, "value", "none", os_type_STRING); + else { + log_debug(ZONE, "invalid value '%.*s' on s10n item, failing request", NAD_AVAL_L(pkt->nad, value), NAD_AVAL(pkt->nad, value)); + pool_free(p); + os_free(os); + return -stanza_err_BAD_REQUEST; + } + + log_debug(ZONE, "s10n item with value '%.*s' (to %d from %d)", NAD_AVAL_L(pkt->nad, value), NAD_AVAL(pkt->nad, value), zitem->to, zitem->from); + } + } + + /* action */ + if(NAD_AVAL_L(pkt->nad, action) == 4 && strncmp("deny", NAD_AVAL(pkt->nad, action), 4) == 0) { + zitem->deny = 1; + log_debug(ZONE, "deny rule"); + } else if(NAD_AVAL_L(pkt->nad, action) == 5 && strncmp("allow", NAD_AVAL(pkt->nad, action), 5) == 0) { + zitem->deny = 0; + log_debug(ZONE, "allow rule"); + } else { + log_debug(ZONE, "unknown action '%.*s', failing request", NAD_AVAL_L(pkt->nad, action), NAD_AVAL(pkt->nad, action)); + pool_free(p); + os_free(os); + return -stanza_err_BAD_REQUEST; + } + + os_object_put(o, "deny", &zitem->deny, os_type_BOOLEAN); + + /* order */ + snprintf(corder, 14, "%.*s", NAD_AVAL_L(pkt->nad, order), NAD_AVAL(pkt->nad, order)); + corder[13] = '\0'; + zitem->order = atoi(corder); + + os_object_put(o, "order", &zitem->order, os_type_INTEGER); + + /* block types */ + if(nad_find_elem(pkt->nad, item, ns, "message", 1) >= 0) + zitem->block |= block_MESSAGE; + if(nad_find_elem(pkt->nad, item, ns, "presence-in", 1) >= 0) + zitem->block |= block_PRES_IN; + if(nad_find_elem(pkt->nad, item, ns, "presence-out", 1) >= 0) + zitem->block |= block_PRES_OUT; + if(nad_find_elem(pkt->nad, item, ns, "iq", 1) >= 0) + zitem->block |= block_IQ; + + os_object_put(o, "block", &zitem->block, os_type_INTEGER); + + /* insert it */ + for(scan = zlist->items; scan != NULL; scan = scan->next) + if(zitem->order < scan->order) + break; + + /* we're >= everyone, add us to the end */ + if(scan == NULL) { + if(zlist->last == NULL) + zlist->items = zlist->last = zitem; + else { + zlist->last->next = zitem; + zitem->prev = zlist->last; + zlist->last = zitem; + } + } + + /* insert just before scan */ + else { + if(zlist->items == scan) { + zitem->next = zlist->items; + zlist->items = zitem; + scan->prev = zitem; + } else { + zitem->next = scan; + zitem->prev = scan->prev; + scan->prev->next = zitem; + scan->prev = zitem; + } + } + + /* next item */ + item = nad_find_elem(pkt->nad, item, ns, "item", 0); + } + + /* write the whole list out */ + sprintf(filter, "(list=%i:%s)", strlen(zlist->name), zlist->name); + + ret = storage_replace(mod->mm->sm->st, "privacy-items", jid_user(sess->user->jid), filter, os); + os_free(os); + + /* failed! */ + if(ret != st_SUCCESS) { + pool_free(zlist->p); + return -stanza_err_INTERNAL_SERVER_ERROR; + } + + /* old list pointer */ + old = xhash_get(z->lists, zlist->name); + + /* removed list */ + if(zlist->items == NULL) { + log_debug(ZONE, "removed list %s", zlist->name); + xhash_zap(z->lists, zlist->name); + pool_free(zlist->p); + if(old != NULL) pool_free(old->p); + zlist = NULL; + } else { + log_debug(ZONE, "updated list %s", zlist->name); + xhash_put(z->lists, zlist->name, (void *) zlist); + if(old != NULL) pool_free(old->p); + } + + /* if this was a new list, then noone has it active yet */ + if(old != NULL) { + + /* relink */ + log_debug(ZONE, "relinking sessions"); + + /* loop through sessions, relink */ + for(sscan = sess->user->sessions; sscan != NULL; sscan = sscan->next) + if(sscan->module_data[mod->index] == old) { + sscan->module_data[mod->index] = (void *) zlist; + log_debug(ZONE, "session '%s' now has active list '%s'", jid_full(sscan->jid), (zlist != NULL) ? zlist->name : "(NONE)"); + } + + /* default list */ + if(z->def == old) { + z->def = zlist; + + if(zlist == NULL) { + storage_delete(mod->mm->sm->st, "privacy-default", jid_user(sess->user->jid), NULL); + log_debug(ZONE, "removed default list"); + } + + else { + os = os_new(); + o = os_object_new(os); + + os_object_put(o, "default", zlist->name, os_type_STRING); + + storage_replace(mod->mm->sm->st, "privacy-default", jid_user(sess->user->jid), NULL, os); + + os_free(os); + + log_debug(ZONE, "default list is now '%s'", (zlist != NULL) ? zlist->name : "(NONE)"); + } + } + } + } + + /* set the active list */ + if(active >= 0) { + name = nad_find_attr(pkt->nad, active, -1, "name", NULL); + if(name < 0) { + /* no name, no active list */ + log_debug(ZONE, "clearing active list for session '%s'", jid_full(sess->jid)); + sess->module_data[mod->index] = NULL; + } + + else { + snprintf(str, 256, "%.*s", NAD_AVAL_L(pkt->nad, name), NAD_AVAL(pkt->nad, name)); + str[255] = '\0'; + + zlist = xhash_get(z->lists, str); + if(zlist == NULL) { + log_debug(ZONE, "request to make list '%s' active, but there's no such list", str); + + /* hack the error in */ + pkt_error(pkt, stanza_err_ITEM_NOT_FOUND); + + pkt_sess(pkt, sess); + return mod_HANDLED; + } + + sess->module_data[mod->index] = zlist; + + log_debug(ZONE, "session '%s' now has active list '%s'", jid_full(sess->jid), str); + } + } + + /* set the default list */ + if(def >= 0) { + name = nad_find_attr(pkt->nad, def, -1, "name", NULL); + if(name < 0) { + /* no name, no default list */ + log_debug(ZONE, "clearing default list for '%s'", jid_user(sess->user->jid)); + z->def = NULL; + } + + else { + snprintf(str, 256, "%.*s", NAD_AVAL_L(pkt->nad, name), NAD_AVAL(pkt->nad, name)); + str[255] = '\0'; + + zlist = xhash_get(z->lists, str); + if(zlist == NULL) { + log_debug(ZONE, "request to make list '%s' default, but there's no such list"); + + /* hack the error in */ + pkt_error(pkt, stanza_err_ITEM_NOT_FOUND); + + pkt_sess(pkt, sess); + return mod_HANDLED; + } + + z->def = zlist; + + os = os_new(); + o = os_object_new(os); + + os_object_put(o, "default", zlist->name, os_type_STRING); + + storage_replace(mod->mm->sm->st, "privacy-default", jid_user(sess->user->jid), NULL, os); + + os_free(os); + + log_debug(ZONE, "'%s' now has default list '%s'", jid_user(sess->user->jid), str); + } + } + + /* done, let them know */ + result = pkt_create(pkt->sm, "iq", "result", NULL, NULL); + + pkt_id(pkt, result); + + /* done with this */ + pkt_free(pkt); + + /* give it to the session */ + pkt_sess(result, sess); + + /* all done */ + return mod_HANDLED; + } + + /* its a get */ + + /* only allowed to request one list, if any */ + list = nad_find_elem(pkt->nad, query, ns, "list", 1); + if(list >= 0 && nad_find_elem(pkt->nad, list, ns, "list", 0) >= 0) { + /* hack the error in */ + pkt_error(pkt, stanza_err_BAD_REQUEST); + + pkt_sess(pkt, sess); + return mod_HANDLED; + } + + result = pkt_create(pkt->sm, "iq", "result", NULL, NULL); + + pkt_id(pkt, result); + + ns = nad_add_namespace(result->nad, uri_PRIVACY, NULL); + query = nad_insert_elem(result->nad, 1, ns, "query", NULL); + + /* just do one */ + if(list >= 0) { + name = nad_find_attr(pkt->nad, list, -1, "name", NULL); + + zlist = xhash_getx(z->lists, NAD_AVAL(pkt->nad, name), NAD_AVAL_L(pkt->nad, name)); + if(zlist == NULL) { + /* hack the error in */ + pkt_error(pkt, stanza_err_ITEM_NOT_FOUND); + + pkt_sess(pkt, sess); + return mod_HANDLED; + } + + _privacy_result_builder(z->lists, zlist->name, (void *) zlist, (void *) result); + } + + else { + /* walk the list hash and add the lists in */ + xhash_walk(z->lists, _privacy_lists_result_builder, (void *) result); + } + + /* tell them about current active and default list if they asked for everything */ + if(list < 0) { + /* active */ + if(sess->module_data[mod->index] != NULL) { + active = nad_insert_elem(result->nad, query, ns, "active", NULL); + nad_set_attr(result->nad, active, -1, "name", ((zebra_list_t) sess->module_data[mod->index])->name, 0); + } + + /* and the default list */ + if(z->def != NULL) { + def = nad_insert_elem(result->nad, query, ns, "default", NULL); + nad_set_attr(result->nad, def, -1, "name", z->def->name, 0); + } + } + + /* give it to the session */ + pkt_sess(result, sess); + + /* done with this */ + pkt_free(pkt); + + /* all done */ + return mod_HANDLED; +} + +static void _privacy_user_delete(mod_instance_t mi, jid_t jid) { + log_debug(ZONE, "deleting privacy data for %s", jid_user(jid)); + + storage_delete(mi->sm->st, "privacy-items", jid_user(jid), NULL); + storage_delete(mi->sm->st, "privacy-default", jid_user(jid), NULL); +} + +static void _privacy_free(module_t mod) { + sm_unregister_ns(mod->mm->sm, uri_PRIVACY); + feature_unregister(mod->mm->sm, uri_PRIVACY); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if (mod->init) return 0; + + mod->user_load = _privacy_user_load; + mod->in_router = _privacy_in_router; + mod->out_router = _privacy_out_router; + mod->in_sess = _privacy_in_sess; + mod->user_delete = _privacy_user_delete; + mod->free = _privacy_free; + + ns_PRIVACY = sm_register_ns(mod->mm->sm, uri_PRIVACY); + feature_register(mod->mm->sm, uri_PRIVACY); + + return 0; +} diff --git a/sm/mod_roster.c b/sm/mod_roster.c new file mode 100644 index 00000000..daf15556 --- /dev/null +++ b/sm/mod_roster.c @@ -0,0 +1,691 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_roster.c + * @brief roster managment & subscriptions + * @author Robert Norris + * $Date: 2005/09/09 05:34:13 $ + * $Revision: 1.61 $ + */ + +/** free a single roster item */ +static void _roster_free_walker(xht roster, const char *key, void *val, void *arg) +{ + item_t item = (item_t) val; + int i; + + jid_free(item->jid); + + if(item->name != NULL) + free(item->name); + + for(i = 0; i < item->ngroups; i++) + free(item->groups[i]); + free(item->groups); + + free(item); +} + +/** free the roster */ +static void _roster_free(user_t user) +{ + if(user->roster == NULL) + return; + + log_debug(ZONE, "freeing roster for %s", jid_user(user->jid)); + + xhash_walk(user->roster, _roster_free_walker, NULL); + + xhash_free(user->roster); + user->roster = NULL; +} + +static void _roster_save_item(user_t user, item_t item) { + os_t os; + os_object_t o; + char filter[4096]; + int i; + + log_debug(ZONE, "saving roster item %s for %s", jid_full(item->jid), jid_user(user->jid)); + + os = os_new(); + o = os_object_new(os); + + os_object_put(o, "jid", jid_full(item->jid), os_type_STRING); + + if(item->name != NULL) + os_object_put(o, "name", item->name, os_type_STRING); + + os_object_put(o, "to", &item->to, os_type_BOOLEAN); + os_object_put(o, "from", &item->from, os_type_BOOLEAN); + os_object_put(o, "ask", &item->ask, os_type_INTEGER); + + snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(item->jid)), jid_full(item->jid)); + + storage_replace(user->sm->st, "roster-items", jid_user(user->jid), filter, os); + + os_free(os); + + snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(item->jid)), jid_full(item->jid)); + + if(item->ngroups == 0) { + storage_delete(user->sm->st, "roster-groups", jid_user(user->jid), filter); + return; + } + + os = os_new(); + + for(i = 0; i < item->ngroups; i++) { + o = os_object_new(os); + + os_object_put(o, "jid", jid_full(item->jid), os_type_STRING); + os_object_put(o, "group", item->groups[i], os_type_STRING); + } + + storage_replace(user->sm->st, "roster-groups", jid_user(user->jid), filter, os); + + os_free(os); +} + +/** insert a roster item into this pkt, starting at elem */ +static void _roster_insert_item(pkt_t pkt, item_t item, int elem) +{ + int ns, i; + char *sub; + + ns = nad_add_namespace(pkt->nad, uri_CLIENT, NULL); + elem = nad_insert_elem(pkt->nad, elem, ns, "item", NULL); + nad_set_attr(pkt->nad, elem, -1, "jid", jid_full(item->jid), 0); + + if(item->to && item->from) + sub = "both"; + else if(item->to) + sub = "to"; + else if(item->from) + sub = "from"; + else + sub = "none"; + + nad_set_attr(pkt->nad, elem, -1, "subscription", sub, 0); + + if(item->ask == 1) + nad_set_attr(pkt->nad, elem, -1, "ask", "subscribe", 9); + else if(item->ask == 2) + nad_set_attr(pkt->nad, elem, -1, "ask", "unsubscribe", 11); + + if(item->name != NULL) + nad_set_attr(pkt->nad, elem, -1, "name", item->name, 0); + + for(i = 0; i < item->ngroups; i++) + nad_insert_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "group", item->groups[i]); +} + +/** push this packet to all sessions except the given one */ +static void _roster_push(user_t user, pkt_t pkt, int mod_index) +{ + sess_t scan; + pkt_t push; + + /* do the push */ + for(scan = user->sessions; scan != NULL; scan = scan->next) + { + /* don't push to us or to anyone who hasn't loaded the roster */ + if((int) scan->module_data[mod_index] == 0) + continue; + + push = pkt_dup(pkt, jid_full(scan->jid), NULL); + pkt_sess(push, scan); + } +} + +static mod_ret_t _roster_in_sess_s10n(mod_instance_t mi, sess_t sess, pkt_t pkt) +{ + module_t mod = mi->mod; + item_t item; + pkt_t push; + int ns, elem; + + log_debug(ZONE, "got s10n packet"); + + /* s10ns have to go to someone */ + if(pkt->to == NULL) + return -stanza_err_BAD_REQUEST; + + /* add a proper from address (no resource) */ + if(pkt->from != NULL) + jid_free(pkt->from); + + pkt->from = jid_new(mod->mm->sm->pc, jid_user(sess->jid), -1); + nad_set_attr(pkt->nad, 1, -1, "from", jid_full(pkt->from), 0); + + /* see if they're already on the roster */ + item = xhash_get(sess->user->roster, jid_full(pkt->to)); + if(item == NULL) + { + /* if they're not on the roster, there's no subscription, + * so quietly pass it on */ + if(pkt->type == pkt_S10N_UN || pkt->type == pkt_S10N_UNED) + return mod_PASS; + + /* make a new one */ + item = (item_t) malloc(sizeof(struct item_st)); + memset(item, 0, sizeof(struct item_st)); + + item->jid = jid_dup(pkt->to); + + /* remember it */ + xhash_put(sess->user->roster, jid_full(item->jid), (void *) item); + + log_debug(ZONE, "made new empty roster item for %s", jid_full(item->jid)); + } + + /* a request */ + if(pkt->type == pkt_S10N) + item->ask = 1; + else if(pkt->type == pkt_S10N_UN) + item->ask = 2; + + /* changing states */ + else if(pkt->type == pkt_S10N_ED) + { + /* they're allowed to see us, send them presence */ + item->ask = 0; + item->from = 1; + pres_roster(sess, item); + } + else if(pkt->type == pkt_S10N_UNED) + { + /* they're not allowed to see us anymore */ + item->ask = 0; + item->from = 0; + pres_roster(sess, item); + } + + /* save changes */ + _roster_save_item(sess->user, item); + + /* build a new packet to push out to everyone */ + push = pkt_create(sess->user->sm, "iq", "set", NULL, NULL); + pkt_id_new(push); + ns = nad_add_namespace(push->nad, uri_ROSTER, NULL); + elem = nad_append_elem(push->nad, ns, "query", 3); + + _roster_insert_item(push, item, elem); + + /* tell everyone */ + _roster_push(sess->user, push, mod->index); + + /* everyone knows */ + pkt_free(push); + + /* pass it on */ + return mod_PASS; +} + +/** build the iq:roster packet from the hash */ +static void _roster_get_walker(xht roster, const char *id, void *val, void *arg) +{ + item_t item = (item_t) val; + pkt_t pkt = (pkt_t) arg; + + _roster_insert_item(pkt, item, 2); +} + +static void _roster_set_item(pkt_t pkt, int elem, sess_t sess, mod_instance_t mi) +{ + module_t mod = mi->mod; + int attr, ns, i; + jid_t jid; + item_t item; + pkt_t push; + char filter[4096]; + + /* extract the jid */ + attr = nad_find_attr(pkt->nad, elem, -1, "jid", NULL); + jid = jid_new(pkt->sm->pc, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + if(jid == NULL) { + log_debug(ZONE, "jid failed prep check, skipping"); + return; + } + + /* check for removals */ + if(nad_find_attr(pkt->nad, elem, -1, "subscription", "remove") >= 0) + { + /* trash the item */ + item = xhash_get(sess->user->roster, jid_full(jid)); + if(item != NULL) + { + /* tell them they're unsubscribed */ + if(item->from) { + log_debug(ZONE, "telling %s that they're unsubscribed", jid_user(item->jid)); + pkt_router(pkt_create(sess->user->sm, "presence", "unsubscribed", jid_user(item->jid), jid_user(sess->jid))); + } + item->from = 0; + + /* tell them to unsubscribe us */ + if(item->to) { + log_debug(ZONE, "unsubscribing from %s", jid_user(item->jid)); + pkt_router(pkt_create(sess->user->sm, "presence", "unsubscribe", jid_user(item->jid), jid_user(sess->jid))); + } + item->to = 0; + + /* send unavailable */ + pres_roster(sess, item); + + /* kill it */ + xhash_zap(sess->user->roster, jid_full(jid)); + _roster_free_walker(NULL, (const char *) jid_full(jid), (void *) item, NULL); + + snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(jid)), jid_full(jid)); + storage_delete(sess->user->sm->st, "roster-items", jid_user(sess->jid), filter); + + snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(jid)), jid_full(jid)); + storage_delete(sess->user->sm->st, "roster-groups", jid_user(sess->jid), filter); + } + + log_debug(ZONE, "removed %s from roster", jid_full(jid)); + + /* build a new packet to push out to everyone */ + push = pkt_create(sess->user->sm, "iq", "set", NULL, NULL); + pkt_id_new(push); + ns = nad_add_namespace(push->nad, uri_ROSTER, NULL); + + nad_append_elem(push->nad, ns, "query", 3); + elem = nad_append_elem(push->nad, ns, "item", 4); + nad_set_attr(push->nad, elem, -1, "jid", jid_full(jid), 0); + nad_set_attr(push->nad, elem, -1, "subscription", "remove", 6); + + /* tell everyone */ + _roster_push(sess->user, push, mod->index); + + /* we're done */ + pkt_free(push); + + jid_free(jid); + + return; + } + + /* find a pre-existing one */ + item = xhash_get(sess->user->roster, jid_full(jid)); + if(item == NULL) + { + /* make a new one */ + item = (item_t) malloc(sizeof(struct item_st)); + memset(item, 0, sizeof(struct item_st)); + + /* add the jid */ + item->jid = jid; + + /* add it to the roster */ + xhash_put(sess->user->roster, jid_full(item->jid), (void *) item); + + log_debug(ZONE, "created new roster item %s", jid_full(item->jid)); + } + + else + jid_free(jid); + + /* free the old name */ + if(item->name != NULL) { + free(item->name); + item->name = NULL; + } + + /* extract the name */ + attr = nad_find_attr(pkt->nad, elem, -1, "name", NULL); + if(attr >= 0) + { + item->name = (char *) malloc(sizeof(char) * (NAD_AVAL_L(pkt->nad, attr) + 1)); + sprintf(item->name, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr)); + } + + /* free the old groups */ + if(item->groups != NULL) + { + for(i = 0; i < item->ngroups; i++) + free(item->groups[i]); + free(item->groups); + item->ngroups = 0; + item->groups = NULL; + } + + /* loop over the groups, adding them to the array */ + elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "group", 1); + while(elem >= 0) + { + /* empty group tags get skipped */ + if(NAD_CDATA_L(pkt->nad, elem) >= 0) + { + /* make room and shove it in */ + item->groups = (char **) realloc(item->groups, sizeof(char *) * (item->ngroups + 1)); + + item->groups[item->ngroups] = (char *) malloc(sizeof(char) * (NAD_CDATA_L(pkt->nad, elem) + 1)); + sprintf(item->groups[item->ngroups], "%.*s", NAD_CDATA_L(pkt->nad, elem), NAD_CDATA(pkt->nad, elem)); + + item->ngroups++; + } + + elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "group", 0); + } + + log_debug(ZONE, "added %s to roster (to %d from %d ask %d name %s ngroups %d)", jid_full(item->jid), item->to, item->from, item->ask, item->name, item->ngroups); + + /* save changes */ + _roster_save_item(sess->user, item); + + /* build a new packet to push out to everyone */ + push = pkt_create(sess->user->sm, "iq", "set", NULL, NULL); + pkt_id_new(push); + ns = nad_add_namespace(push->nad, uri_ROSTER, NULL); + elem = nad_append_elem(push->nad, ns, "query", 3); + + _roster_insert_item(push, item, elem); + + /* tell everyone */ + _roster_push(sess->user, push, mod->index); + + /* we're done */ + pkt_free(push); +} + +/** our main handler for packets arriving from a session */ +static mod_ret_t _roster_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) +{ + module_t mod = mi->mod; + int elem, attr; + pkt_t result; + + /* handle s10ns in a different function */ + if(pkt->type & pkt_S10N) + return _roster_in_sess_s10n(mi, sess, pkt); + + /* we only want to play with iq:roster packets */ + if(pkt->ns != ns_ROSTER) + return mod_PASS; + + /* quietly drop results, its probably them responding to a push */ + if(pkt->type == pkt_IQ_RESULT) { + pkt_free(pkt); + return mod_HANDLED; + } + + /* need gets or sets */ + if(pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET) + return mod_PASS; + + /* get */ + if(pkt->type == pkt_IQ) + { + /* build the packet */ + xhash_walk(sess->user->roster, _roster_get_walker, (void *) pkt); + + nad_set_attr(pkt->nad, 1, -1, "type", "result", 6); + pkt_sess(pkt, sess); + + /* remember that they loaded it, so we know to push updates to them */ + sess->module_data[mod->index] = (void *) 1; + + return mod_HANDLED; + } + + /* set, find the item */ + elem = nad_find_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 2), "item", 1); + if(elem < 0) + /* no item, abort */ + return -stanza_err_BAD_REQUEST; + + /* loop over items and stick them in */ + while(elem >= 0) + { + /* extract the jid */ + attr = nad_find_attr(pkt->nad, elem, -1, "jid", NULL); + if(attr < 0 || NAD_AVAL_L(pkt->nad, attr) == 0) + { + log_debug(ZONE, "no jid on this item, aborting"); + + /* no jid, abort */ + return -stanza_err_BAD_REQUEST; + } + + /* utility */ + _roster_set_item(pkt, elem, sess, mi); + + /* next one */ + elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "item", 0); + } + + /* send the result */ + result = pkt_create(sess->user->sm, "iq", "result", NULL, NULL); + + pkt_id(pkt, result); + + /* tell them */ + pkt_sess(result, sess); + + /* free the request */ + pkt_free(pkt); + + return mod_HANDLED; +} + +/** handle incoming s10ns */ +static mod_ret_t _roster_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) +{ + module_t mod = mi->mod; + item_t item; + int ns, elem; + + /* only want s10ns */ + if(!(pkt->type & pkt_S10N)) + return mod_PASS; + + /* drop route errors */ + if(pkt->rtype & route_ERROR) { + pkt_free(pkt); + return mod_HANDLED; + } + + /* get the roster item */ + item = (item_t) xhash_get(user->roster, jid_full(pkt->from)); + if(item == NULL) { + /* they're not on the roster, so subs / unsubs go direct */ + if(pkt->type == pkt_S10N || pkt->type == pkt_S10N_UN) + return mod_PASS; + + /* we didn't ask for this, so we don't care */ + pkt_free(pkt); + return mod_HANDLED; + } + + /* trying to subscribe */ + if(pkt->type == pkt_S10N) + { + if(item->from) + { + /* already subscribed, tell them */ + nad_set_attr(pkt->nad, 1, -1, "type", "subscribed", 10); + pkt_router(pkt_tofrom(pkt)); + + /* update their presence from the leading session */ + if(user->top != NULL) + pres_roster(user->top, item); + + return mod_HANDLED; + } + + return mod_PASS; + } + + /* trying to unsubscribe */ + if(pkt->type == pkt_S10N_UN) + { + if(!item->from) + { + /* already unsubscribed, tell them */ + nad_set_attr(pkt->nad, 1, -1, "type", "unsubscribed", 12); + pkt_router(pkt_tofrom(pkt)); + + /* update their presence from the leading session */ + if(user->top != NULL) + pres_roster(user->top, item); + + return mod_HANDLED; + } + + return mod_PASS; + } + + /* update our s10n */ + if(pkt->type == pkt_S10N_ED) + item->to = 1; + else + item->to = 0; + + item->ask = 0; + + /* save changes */ + _roster_save_item(user, item); + + /* if there's no sessions, then we're done */ + if(user->sessions == NULL) + return mod_PASS; + + /* build a new packet to push out to everyone */ + pkt = pkt_create(user->sm, "iq", "set", NULL, NULL); + pkt_id_new(pkt); + ns = nad_add_namespace(pkt->nad, uri_ROSTER, NULL); + elem = nad_append_elem(pkt->nad, ns, "query", 3); + + _roster_insert_item(pkt, item, elem); + + /* tell everyone */ + _roster_push(user, pkt, mod->index); + + /* everyone knows */ + pkt_free(pkt); + + return mod_PASS; +} + +/** load the roster from the database */ +static int _roster_user_load(mod_instance_t mi, user_t user) { + os_t os; + os_object_t o; + char *str; + item_t item, olditem; + + log_debug(ZONE, "loading roster for %s", jid_user(user->jid)); + + user->roster = xhash_new(101); + + /* pull all the items */ + if(storage_get(user->sm->st, "roster-items", jid_user(user->jid), NULL, &os) == st_SUCCESS) { + if(os_iter_first(os)) + do { + o = os_iter_object(os); + + if(os_object_get_str(os, o, "jid", &str)) { + /* new one */ + item = (item_t) malloc(sizeof(struct item_st)); + memset(item, 0, sizeof(struct item_st)); + + item->jid = jid_new(mi->mod->mm->sm->pc, str, -1); + if(item->jid == NULL) { + log_debug(ZONE, "eek! invalid jid %s, skipping it", str); + free(item); + + } else { + if(os_object_get_str(os, o, "name", &str)) + item->name = strdup(str); + + os_object_get_bool(os, o, "to", &item->to); + os_object_get_bool(os, o, "from", &item->from); + os_object_get_int(os, o, "ask", &item->ask); + + olditem = xhash_get(user->roster, jid_full(item->jid)); + if(olditem) { + log_debug(ZONE, "removing old %s roster entry", jid_full(item->jid)); + xhash_zap(user->roster, jid_full(item->jid)); + _roster_free_walker(user->roster, jid_full(item->jid), (void *) olditem, NULL); + } + + /* its good */ + xhash_put(user->roster, jid_full(item->jid), (void *) item); + + log_debug(ZONE, "added %s to roster (to %d from %d ask %d name %s)", + jid_full(item->jid), item->to, item->from, item->ask, item->name); + } + } + } while(os_iter_next(os)); + + os_free(os); + } + + /* pull the groups and match them up */ + if(storage_get(user->sm->st, "roster-groups", jid_user(user->jid), NULL, &os) == st_SUCCESS) { + if(os_iter_first(os)) + do { + o = os_iter_object(os); + + if(os_object_get_str(os, o, "jid", &str)) { + item = xhash_get(user->roster, str); + + if(item != NULL && os_object_get_str(os, o, "group", &str)) { + item->groups = realloc(item->groups, sizeof(char *) * (item->ngroups + 1)); + item->groups[item->ngroups] = strdup(str); + item->ngroups++; + + log_debug(ZONE, "added group %s to item %s", str, jid_full(item->jid)); + } + } + } while(os_iter_next(os)); + + os_free(os); + } + + pool_cleanup(user->p, (void (*))(void *) _roster_free, user); + + return 0; +} + +static void _roster_user_delete(mod_instance_t mi, jid_t jid) { + log_debug(ZONE, "deleting roster data for %s", jid_user(jid)); + + storage_delete(mi->sm->st, "roster-items", jid_user(jid), NULL); + storage_delete(mi->sm->st, "roster-groups", jid_user(jid), NULL); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->in_sess = _roster_in_sess; + mod->pkt_user = _roster_pkt_user; + mod->user_load = _roster_user_load; + mod->user_delete = _roster_user_delete; + + feature_register(mod->mm->sm, uri_ROSTER); + + return 0; +} diff --git a/sm/mod_session.c b/sm/mod_session.c new file mode 100644 index 00000000..d49794cb --- /dev/null +++ b/sm/mod_session.c @@ -0,0 +1,322 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_session.c + * @brief session control + * @author Robert Norris + * $Date: 2006/03/09 09:08:14 $ + * $Revision: 1.12 $ + */ + +/** session packet handling + * + * - if packet has session namespace on first element, its from a c2s + * + * - if its pkt_SESSION, then its some session action + * - if its pkt_SESSION_START, start a session, reply, done + * - get the sm id, find the corresponding session + * - do the action, reply, done + * + * - otherwise, its a normal message/presence/iq for a session + * - get the sm id, find the corresponding session + * - hand to in_sess chain + * + */ + +/* union for xhash_iter_get to comply with strict-alias rules for gcc3 */ +union xhashv +{ + void **val; + sess_t *sess_val; +}; + +static mod_ret_t _session_in_router(mod_instance_t mi, pkt_t pkt) { + sm_t sm = mi->mod->mm->sm; + int ns, attr; + jid_t jid; + sess_t sess = (sess_t) NULL; + mod_ret_t ret; + + /* if we've got this namespace, its from a c2s */ + if(pkt->nad->ecur <= 1 || (ns = nad_find_namespace(pkt->nad, 1, uri_SESSION, NULL)) < 0) + return mod_PASS; + + /* don't bother if its a failure */ + if(pkt->type & pkt_SESS_FAILED) { + /* !!! check failed=1, handle */ + pkt_free(pkt); + return mod_HANDLED; + } + + /* session commands */ + if(pkt->type & pkt_SESS) { + + ns = nad_find_namespace(pkt->nad, 1, uri_SESSION, NULL); + + /* only end can get away without a target */ + attr = nad_find_attr(pkt->nad, 1, -1, "target", NULL); + if(attr < 0 && pkt->type != pkt_SESS_END) { + nad_set_attr(pkt->nad, 1, ns, "failed", "1", 1); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + + return mod_HANDLED; + } + + /* session start */ + if(pkt->type == pkt_SESS) { + jid = jid_new(sm->pc, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + + if(jid != NULL) + sess = sess_start(sm, jid); + + if(jid == NULL || sess == NULL) { + nad_set_attr(pkt->nad, 1, ns, "failed", "1", 1); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + if(jid != NULL) + jid_free(jid); + + return mod_HANDLED; + } + + /* c2s component that is handling this session */ + strcpy(sess->c2s, pkt->rfrom->domain); + + /* remember what c2s calls us */ + attr = nad_find_attr(pkt->nad, 1, ns, "c2s", NULL); + snprintf(sess->c2s_id, 10, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr)); + + /* add our id */ + nad_set_attr(pkt->nad, 1, ns, "sm", sess->sm_id, 0); + + /* inform c2s */ + nad_set_attr(pkt->nad, 1, -1, "action", "started", 7); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + jid_free(jid); + + return mod_HANDLED; + } + + /* user create */ + if(pkt->type == pkt_SESS_CREATE) { + jid = jid_new(sm->pc, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + + if(jid == NULL || user_create(sm, jid) != 0) { + nad_set_attr(pkt->nad, 1, ns, "failed", "1", 1); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + if(jid != NULL) + jid_free(jid); + + return mod_HANDLED; + } + + /* inform c2s */ + nad_set_attr(pkt->nad, 1, -1, "action", "created", 7); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + jid_free(jid); + + return mod_HANDLED; + } + + /* user delete */ + if(pkt->type == pkt_SESS_DELETE) { + jid = jid_new(sm->pc, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + if(jid == NULL) { + pkt_free(pkt); + return mod_HANDLED; + } + + user_delete(sm, jid); + + /* inform c2s */ + nad_set_attr(pkt->nad, 1, -1, "action", "deleted", 7); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + jid_free(jid); + + return mod_HANDLED; + } + + /* get the session id */ + attr = nad_find_attr(pkt->nad, 1, ns, "sm", NULL); + if(attr < 0) { + log_debug(ZONE, "no session id, bouncing"); + nad_set_attr(pkt->nad, 1, ns, "failed", "1", 1); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + + return mod_HANDLED; + } + + /* find the corresponding session */ + sess = xhash_getx(sm->sessions, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + + /* active check */ + if(sess == NULL) { + log_debug(ZONE, "session %.*s doesn't exist, bouncing", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr)); + nad_set_attr(pkt->nad, 1, ns, "failed", "1", 1); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + + return mod_HANDLED; + } + + /* make sure its from them */ + attr = nad_find_attr(pkt->nad, 1, ns, "c2s", NULL); + if(attr >= 0 && (strlen(sess->c2s_id) != NAD_AVAL_L(pkt->nad, attr) || strncmp(sess->c2s_id, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)) != 0)) { + log_debug(ZONE, "invalid sender on route from %s for session %s (should be %s)", pkt->rfrom->domain, sess->sm_id, sess->c2s_id); + pkt_free(pkt); + return mod_HANDLED; + } + + /* session end */ + if(pkt->type == pkt_SESS_END) { + sm_c2s_action(sess, "ended", NULL); + sess_end(sess); + + pkt_free(pkt); + return mod_HANDLED; + } + + log_debug(ZONE, "unknown session packet, dropping"); + pkt_free(pkt); + + return mod_HANDLED; + } + + /* otherwise, its a normal packet for the session */ + + /* get the session id */ + attr = nad_find_attr(pkt->nad, 1, ns, "sm", NULL); + if(attr < 0) { + log_debug(ZONE, "no session id, bouncing"); + nad_set_attr(pkt->nad, 1, ns, "failed", "1", 1); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + + return mod_HANDLED; + } + + /* find the corresponding session */ + sess = xhash_getx(sm->sessions, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + + /* active check */ + if(sess == NULL) { + log_debug(ZONE, "session %.*s doesn't exist, bouncing", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr)); + nad_set_attr(pkt->nad, 1, ns, "failed", "1", 1); + sx_nad_write(sm->router, stanza_tofrom(pkt->nad, 0)); + + pkt->nad = NULL; + pkt_free(pkt); + + return mod_HANDLED; + } + + /* make sure its from them */ + attr = nad_find_attr(pkt->nad, 1, ns, "c2s", NULL); + if(attr >= 0 && (strlen(sess->c2s_id) != NAD_AVAL_L(pkt->nad, attr) || strncmp(sess->c2s_id, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)) != 0)) { + log_debug(ZONE, "invalid sender on route from %s for session %s (should be %s)", jid_full(pkt->rfrom), sess->sm_id, sess->c2s_id); + pkt_free(pkt); + return mod_HANDLED; + } + + /* where it came from */ + pkt->source = sess; + + /* hand it to the modules */ + ret = mm_in_sess(pkt->sm->mm, sess, pkt); + switch(ret) { + case mod_HANDLED: + break; + + case mod_PASS: + /* ignore IQ result packets that haven't been handled - XMPP 9.2.3.4 */ + if(pkt->type == pkt_IQ_RESULT) + break; + else + ret = -stanza_err_FEATURE_NOT_IMPLEMENTED; + + default: + pkt_sess(pkt_error(pkt, -ret), sess); + + break; + } + + return mod_HANDLED; +} + +static mod_ret_t _session_pkt_router(mod_instance_t mi, pkt_t pkt) { + sess_t sess; + union xhashv xhv; + + /* we want unadvertisments */ + if(pkt->from == NULL || !(pkt->rtype & route_ADV) || pkt->rtype != route_ADV_UN) + return mod_PASS; + + log_debug(ZONE, "component '%s' went offline, checking for sessions held there", jid_full(pkt->from)); + + /* this fairly inefficient, especially if we have a lot of sessions + * online, but it shouldn't be called that often (components are usually + * long-running) */ + + xhv.sess_val = &sess; + if(xhash_iter_first(mi->mod->mm->sm->sessions)) + while (xhash_iter_get(mi->mod->mm->sm->sessions, NULL, xhv.val)) { + if(strcmp(sess->c2s, pkt->from->domain) == 0) + sess_end(sess); + else + xhash_iter_next(mi->mod->mm->sm->sessions); + } + + return mod_PASS; +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + if(mi->mod->init) return 0; + + mi->mod->in_router = _session_in_router; + mi->mod->pkt_router = _session_pkt_router; + + return 0; +} diff --git a/sm/mod_template_roster.c b/sm/mod_template_roster.c new file mode 100644 index 00000000..9c08d0be --- /dev/null +++ b/sm/mod_template_roster.c @@ -0,0 +1,269 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_template_roster.c + * @brief user auto-population - roster + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.11 $ + */ + +/* user template - roster */ + +typedef struct _template_roster_st { + sm_t sm; + char *filename; + time_t mtime; + xht items; +} *template_roster_t; + +/* union for xhash_iter_get to comply with strict-alias rules for gcc3 */ +union xhashv +{ + void **val; + item_t *item_val; +}; + +static int _template_roster_reload(template_roster_t tr) { + struct stat st; + FILE *f; + long size; + char *buf; + nad_t nad; + int nitems, eitem, ajid, as10n, aname, egroup; + item_t item; + + if(stat(tr->filename, &st) < 0) { + log_write(tr->sm->log, LOG_ERR, "couldn't stat roster template %s: %s", tr->filename, strerror(errno)); + return 1; + } + + if(st.st_mtime <= tr->mtime) + return 0; + + tr->mtime = st.st_mtime; + + if(tr->items != NULL) + xhash_free(tr->items); + + tr->items = xhash_new(101); + + f = fopen(tr->filename, "r"); + if(f == NULL) { + log_write(tr->sm->log, LOG_ERR, "couldn't open roster template %s: %s", tr->filename, strerror(errno)); + return 1; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + + buf = (char *) malloc(sizeof(char) * size); + + fread(buf, 1, size, f); + if(ferror(f)) { + log_write(tr->sm->log, LOG_ERR, "couldn't read from roster template %s: %s", tr->filename, strerror(errno)); + free(buf); + fclose(f); + return 1; + } + + fclose(f); + + nad = nad_parse(tr->sm->router->nad_cache, buf, size); + if(nad == NULL) { + log_write(tr->sm->log, LOG_ERR, "couldn't parse roster template"); + free(buf); + return 1; + } + + free(buf); + + if(nad->ecur < 2) { + log_write(tr->sm->log, LOG_NOTICE, "roster template has no elements"); + } + + nitems = 0; + eitem = nad_find_elem(nad, 0, NAD_ENS(nad, 0), "item", 1); + while(eitem >= 0) { + ajid = nad_find_attr(nad, eitem, -1, "jid", NULL); + if(ajid < 0) { + log_write(tr->sm->log, LOG_ERR, "roster template has item with no jid, skipping"); + continue; + } + + item = (item_t) pmalloco(xhash_pool(tr->items), sizeof(struct item_st)); + + item->jid = jid_new(tr->sm->pc, NAD_AVAL(nad, ajid), NAD_AVAL_L(nad, ajid)); + if(item->jid == NULL) { + log_write(tr->sm->log, LOG_ERR, "roster template has item with invalid jid, skipping"); + continue; + } + pool_cleanup(xhash_pool(tr->items), (void (*)(void *)) jid_free, item->jid); + + as10n = nad_find_attr(nad, eitem, -1, "subscription", NULL); + if(as10n >= 0) { + if(NAD_AVAL_L(nad, as10n) == 2 && strncmp("to", NAD_AVAL(nad, as10n), 2) == 0) + item->to = 1; + else if(NAD_AVAL_L(nad, as10n) == 4 && strncmp("from", NAD_AVAL(nad, as10n), 4) == 0) + item->from = 1; + else if(NAD_AVAL_L(nad, as10n) == 4 && strncmp("both", NAD_AVAL(nad, as10n), 4) == 0) + item->to = item->from = 1; + } + + aname = nad_find_attr(nad, eitem, -1, "name", NULL); + if(aname >= 0) + item->name = pstrdupx(xhash_pool(tr->items), NAD_AVAL(nad, aname), NAD_AVAL_L(nad, aname)); + + egroup = nad_find_elem(nad, eitem, NAD_ENS(nad, 0), "group", 1); + while(egroup >= 0) { + if(NAD_CDATA_L(nad, egroup) <= 0) { + log_write(tr->sm->log, LOG_ERR, "roster template has zero-length group, skipping"); + continue; + } + + item->groups = (char **) realloc(item->groups, sizeof(char *) * (item->ngroups + 1)); + item->groups[item->ngroups] = pstrdupx(xhash_pool(tr->items), NAD_CDATA(nad, egroup), NAD_CDATA_L(nad, egroup)); + item->ngroups++; + + egroup = nad_find_elem(nad, egroup, NAD_ENS(nad, 0), "group", 0); + } + + if(item->groups != NULL) + pool_cleanup(xhash_pool(tr->items), free, item->groups); + + xhash_put(tr->items, jid_full(item->jid), item); + + log_debug(ZONE, "loaded roster template item %s, %d groups", jid_full(item->jid), item->ngroups); + + nitems++; + + eitem = nad_find_elem(nad, eitem, NAD_ENS(nad, 0), "item", 0); + } + + log_write(tr->sm->log, LOG_NOTICE, "loaded %d items from roster template", nitems); + + return 0; +} + +/** !!! this is a cut & paste of _roster_save_time - break it out */ +static void _template_roster_save_item(sm_t sm, jid_t jid, item_t item) { + os_t os; + os_object_t o; + char filter[4096]; + int i; + + log_debug(ZONE, "saving roster item %s for %s", jid_full(item->jid), jid_user(jid)); + + os = os_new(); + o = os_object_new(os); + + os_object_put(o, "jid", jid_full(item->jid), os_type_STRING); + + if(item->name != NULL) + os_object_put(o, "name", item->name, os_type_STRING); + + os_object_put(o, "to", &item->to, os_type_BOOLEAN); + os_object_put(o, "from", &item->from, os_type_BOOLEAN); + os_object_put(o, "ask", &item->ask, os_type_INTEGER); + + snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(item->jid)), jid_full(item->jid)); + + storage_replace(sm->st, "roster-items", jid_user(jid), filter, os); + + os_free(os); + + snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(item->jid)), jid_full(item->jid)); + + if(item->ngroups == 0) { + storage_delete(sm->st, "roster-groups", jid_user(jid), filter); + return; + } + + os = os_new(); + + for(i = 0; i < item->ngroups; i++) { + o = os_object_new(os); + + os_object_put(o, "jid", jid_full(item->jid), os_type_STRING); + os_object_put(o, "group", item->groups[i], os_type_STRING); + } + + storage_replace(sm->st, "roster-groups", jid_user(jid), filter, os); + + os_free(os); +} + +static int _template_roster_user_create(mod_instance_t mi, jid_t jid) { + template_roster_t tr = (template_roster_t) mi->mod->private; + item_t item; + union xhashv xhv; + + if(_template_roster_reload(tr) != 0) + return 0; + + log_debug(ZONE, "populating roster with items from template"); + + if(xhash_iter_first(tr->items)) + do { + xhv.item_val = &item; + xhash_iter_get(tr->items, NULL, xhv.val); + + _template_roster_save_item(tr->sm, jid, item); + } while(xhash_iter_next(tr->items)); + + return 0; +} + +static void _template_roster_free(module_t mod) { + template_roster_t tr = (template_roster_t) mod->private; + + if(tr->items != NULL) + xhash_free(tr->items); + + free(tr); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + char *filename; + template_roster_t tr; + + if(mod->init) return 0; + + filename = config_get_one(mod->mm->sm->config, "user.template.roster", 0); + if(filename == NULL) + return 0; + + tr = (template_roster_t) malloc(sizeof(struct _template_roster_st)); + memset(tr, 0, sizeof(struct _template_roster_st)); + + tr->sm = mod->mm->sm; + tr->filename = filename; + + mod->private = tr; + + mod->user_create = _template_roster_user_create; + mod->free = _template_roster_free; + + return 0; +} diff --git a/sm/mod_vacation.c b/sm/mod_vacation.c new file mode 100644 index 00000000..ac158723 --- /dev/null +++ b/sm/mod_vacation.c @@ -0,0 +1,250 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_vacation.c + * @brief vacation messages + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.13 $ + */ + +#define uri_VACATION "http://jabber.org/protocol/vacation" +static int ns_VACATION = 0; + +typedef struct _vacation_st { + time_t start; + time_t end; + char *msg; +} *vacation_t; + +static mod_ret_t _vacation_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) { + module_t mod = mi->mod; + vacation_t v = sess->user->module_data[mod->index]; + int ns, start, end, msg; + char dt[30]; + pkt_t res; + os_t os; + os_object_t o; + + /* we only want to play with vacation iq packets */ + if((pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET) || pkt->ns != ns_VACATION) + return mod_PASS; + + /* if it has a to, throw it out */ + if(pkt->to != NULL) + return -stanza_err_BAD_REQUEST; + + /* get */ + if(pkt->type == pkt_IQ) { + if(v->msg == NULL) { + res = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL); + pkt_id(pkt, res); + pkt_free(pkt); + + pkt_sess(res, sess); + + return mod_HANDLED; + } + + ns = nad_find_scoped_namespace(pkt->nad, uri_VACATION, NULL); + + if(v->start != 0) { + datetime_out(v->start, dt_DATETIME, dt, 30); + nad_insert_elem(pkt->nad, 2, ns, "start", dt); + } else + nad_insert_elem(pkt->nad, 2, ns, "start", NULL); + + if(v->end != 0) { + datetime_out(v->end, dt_DATETIME, dt, 30); + nad_insert_elem(pkt->nad, 2, ns, "end", dt); + } else + nad_insert_elem(pkt->nad, 2, ns, "end", NULL); + + nad_insert_elem(pkt->nad, 2, ns, "message", v->msg); + + pkt_tofrom(pkt); + nad_set_attr(pkt->nad, 1, -1, "type", "result", 6); + + pkt_sess(pkt, sess); + + return mod_HANDLED; + } + + /* set */ + ns = nad_find_scoped_namespace(pkt->nad, uri_VACATION, NULL); + + start = nad_find_elem(pkt->nad, 2, ns, "start", 1); + end = nad_find_elem(pkt->nad, 2, ns, "end", 1); + msg = nad_find_elem(pkt->nad, 2, ns, "message", 1); + + if(start < 0 || end < 0 || msg < 0) { + /* forget */ + if(v->msg != NULL) { + free(v->msg); + v->msg = NULL; + } + v->start = 0; + v->end = 0; + + storage_delete(mi->sm->st, "vacation-settings", jid_user(sess->jid), NULL); + + res = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL); + pkt_id(pkt, res); + pkt_free(pkt); + + pkt_sess(res, sess); + + return mod_HANDLED; + } + + if(NAD_CDATA_L(pkt->nad, start) > 0) { + strncpy(dt, NAD_CDATA(pkt->nad, start), (30 < NAD_CDATA_L(pkt->nad, start) ? 30 : NAD_CDATA_L(pkt->nad, start))); + v->start = datetime_in(dt); + } else + v->start = 0; + + if(NAD_CDATA_L(pkt->nad, end) > 0) { + strncpy(dt, NAD_CDATA(pkt->nad, end), (30 < NAD_CDATA_L(pkt->nad, end) ? 30 : NAD_CDATA_L(pkt->nad, end))); + v->end = datetime_in(dt); + } else + v->end = 0; + + v->msg = (char *) malloc(sizeof(char) * (NAD_CDATA_L(pkt->nad, msg) + 1)); + strncpy(v->msg, NAD_CDATA(pkt->nad, msg), NAD_CDATA_L(pkt->nad, msg)); + v->msg[NAD_CDATA_L(pkt->nad, msg)] = '\0'; + + os = os_new(); + o = os_object_new(os); + os_object_put(o, "start", &v->start, os_type_INTEGER); + os_object_put(o, "end", &v->end, os_type_INTEGER); + os_object_put(o, "message", v->msg, os_type_STRING); + + if(storage_replace(mod->mm->sm->st, "vacation-settings", jid_user(sess->user->jid), NULL, os) != st_SUCCESS) { + free(v->msg); + v->msg = NULL; + v->start = 0; + v->end = 0; + return -stanza_err_INTERNAL_SERVER_ERROR; + } + + res = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL); + pkt_id(pkt, res); + pkt_free(pkt); + + pkt_sess(res, sess); + + return mod_HANDLED; +} + +static mod_ret_t _vacation_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) { + module_t mod = mi->mod; + vacation_t v = user->module_data[mod->index]; + time_t t; + pkt_t res; + + if(v->msg == NULL) + return mod_PASS; + + /* only want messages, and only if they're offline */ + if(!(pkt->type & pkt_MESSAGE) || user->top != NULL) + return mod_PASS; + + t = time(NULL); + + if(v->start < t && (t < v->end || v->end == 0)) { + res = pkt_create(mod->mm->sm, "message", NULL, jid_full(pkt->from), mod->mm->sm->id); + nad_insert_elem(res->nad, 1, NAD_ENS(res->nad, 1), "subject", "Automated reply"); + nad_insert_elem(res->nad, 1, NAD_ENS(res->nad, 1), "body", v->msg); + pkt_router(res); + + /* !!! remember that we sent this */ + } + + return mod_PASS; +} + +static void _vacation_user_free(vacation_t v) { + if(v->msg != NULL) + free(v->msg); + free(v); +} + +static int _vacation_user_load(mod_instance_t mi, user_t user) { + module_t mod = mi->mod; + vacation_t v; + os_t os; + os_object_t o; + + v = (vacation_t) malloc(sizeof(struct _vacation_st)); + memset(v, 0, sizeof(struct _vacation_st)); + user->module_data[mod->index] = v; + + if(storage_get(mod->mm->sm->st, "vacation-settings", jid_user(user->jid), NULL, &os) == st_SUCCESS) { + if(os_iter_first(os)) { + o = os_iter_object(os); + + if(os_object_get_time(os, o, "start", &v->start) && + os_object_get_time(os, o, "end", &v->end) && + os_object_get_str(os, o, "message", &v->msg)) + v->msg = strdup(v->msg); + else { + v->start = 0; + v->end = 0; + v->msg = NULL; + } + } + + os_free(os); + } + + pool_cleanup(user->p, (void (*))(void *) _vacation_user_free, v); + + return 0; +} + +static void _vacation_user_delete(mod_instance_t mi, jid_t jid) { + log_debug(ZONE, "deleting vacations ettings for %s", jid_user(jid)); + + storage_delete(mi->sm->st, "vacation-settings", jid_user(jid), NULL); +} + +static void _vacation_free(module_t mod) { + sm_unregister_ns(mod->mm->sm, uri_VACATION); + feature_unregister(mod->mm->sm, uri_VACATION); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->in_sess = _vacation_in_sess; + mod->pkt_user = _vacation_pkt_user; + mod->user_load = _vacation_user_load; + mod->user_delete = _vacation_user_delete; + mod->free = _vacation_free; /* mmm good! :) */ + + ns_VACATION = sm_register_ns(mod->mm->sm, uri_VACATION); + feature_register(mod->mm->sm, uri_VACATION); + + return 0; +} diff --git a/sm/mod_validate.c b/sm/mod_validate.c new file mode 100644 index 00000000..37ebd49c --- /dev/null +++ b/sm/mod_validate.c @@ -0,0 +1,55 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/mod_validate.c + * @brief packet validator + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.15 $ + */ + +static mod_ret_t _validate_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) +{ + /* only want message, presence and iq */ + if(!(pkt->type & pkt_MESSAGE || pkt->type & pkt_PRESENCE || pkt->type & pkt_IQ || pkt->type & pkt_S10N)) { + log_debug(ZONE, "we only take message, presence and iq packets"); + return -stanza_err_BAD_REQUEST; + } + + return mod_PASS; +} + +static mod_ret_t _validate_in_router(mod_instance_t mi, pkt_t pkt) +{ + return _validate_in_sess(mi, NULL, pkt); +} + +DLLEXPORT int module_init(mod_instance_t mi, char *arg) { + module_t mod = mi->mod; + + if(mod->init) return 0; + + mod->in_sess = _validate_in_sess; + mod->in_router = _validate_in_router; + + return 0; +} diff --git a/sm/object.c b/sm/object.c new file mode 100644 index 00000000..70f82c8c --- /dev/null +++ b/sm/object.c @@ -0,0 +1,322 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/object.c + * @brief object sets + * @author Robert Norris + * $Date: 2005/06/02 04:48:24 $ + * $Revision: 1.10 $ + */ + +/* union for xhash_iter_get to comply with strict-alias rules for gcc3 */ +union xhashv +{ + void **val; + os_field_t *osf_val; +}; + +os_t os_new(void) { + pool p; + os_t os; + + p = pool_new(); + os = (os_t) pmalloco(p, sizeof(struct os_st)); + + os->p = p; + + return os; +} + +void os_free(os_t os) { + pool_free(os->p); +} + +int os_count(os_t os) { + return os->count; +} + +int os_iter_first(os_t os) { + os->iter = os->head; + + if(os->iter == NULL) + return 0; + + return 1; +} + +int os_iter_next(os_t os) { + if(os->iter == NULL) + return 0; + + os->iter = os->iter->next; + + if(os->iter == NULL) + return 0; + + return 1; +} + +os_object_t os_iter_object(os_t os) { + return os->iter; +} + +os_object_t os_object_new(os_t os) { + os_object_t o; + + log_debug(ZONE, "creating new object"); + + o = (os_object_t) pmalloco(os->p, sizeof(struct os_object_st)); + o->os = os; + + o->hash = xhash_new(51); + + /* make sure that the hash gets freed when the os pool gets freed */ + pool_cleanup(os->p, (pool_cleaner) pool_free, (void *) xhash_pool(o->hash)); + + /* insert at the end, we have to preserve order */ + o->prev = os->tail; + if(os->tail != NULL) os->tail->next = o; + os->tail = o; + if(os->head == NULL) os->head = o; + + os->count++; + + return o; +} + +void os_object_free(os_object_t o) { + log_debug(ZONE, "dropping object"); + + if(o->prev != NULL) + o->prev->next = o->next; + if(o->next != NULL) + o->next->prev = o->prev; + + if(o->os->head == o) + o->os->head = o->next; + if(o->os->tail == o) + o->os->tail = o->prev; + + if(o->os->iter == o) + o->os->iter = o->next; + + o->os->count--; +} + +/* wrappers for os_object_put to avoid breaking strict-aliasing rules in gcc3 */ + +void os_object_put_time(os_object_t o, const char *key, const time_t *val) { + void *ptr = (void *) val; + os_object_put(o, key, ptr, os_type_INTEGER); +} + +void os_object_put(os_object_t o, const char *key, const void *val, os_type_t type) { + os_field_t osf; + nad_t nad; + + log_debug(ZONE, "adding field %s (val %x type %d) to object", key, val, type); + + osf = pmalloco(o->os->p, sizeof(struct os_field_st)); + osf->key = pstrdup(o->os->p, key); + + switch(type) { + case os_type_BOOLEAN: + case os_type_INTEGER: + osf->val = (void *) pmalloco(o->os->p, sizeof(int)); + * (int *) osf->val = * (int *) val; + break; + + case os_type_STRING: + osf->val = (void *) pstrdup(o->os->p, (char *) val); + break; + + case os_type_NAD: + nad = nad_copy((nad_t) val); + + /* make sure that the nad gets freed when the os pool gets freed */ + pool_cleanup(o->os->p, (pool_cleaner) nad_free, (void *) nad); + + osf->val = (void *) nad; + break; + + case os_type_UNKNOWN: + break; + } + + osf->type = type; + + xhash_put(o->hash, osf->key, (void *) osf); +} + +/* wrappers for os_object_get to avoid breaking strict-aliasing rules in gcc3 */ +int os_object_get_nad(os_t os, os_object_t o, const char *key, nad_t *val) { + void *ptr = (void *) val; + int ret; + + ret = os_object_get(os, o, key, &ptr, os_type_NAD, NULL); + *val = (nad_t) ptr; + + return ret; +} + +int os_object_get_str(os_t os, os_object_t o, const char *key, char **val) { + void *ptr = (void *) val; + int ret; + + ret = os_object_get(os, o, key, &ptr, os_type_STRING, NULL); + *val = (char *) ptr; + + return ret; +} + +int os_object_get_int(os_t os, os_object_t o, const char *key, int *val) { + void *ptr = (void *) val; + int ret; + + ret = os_object_get(os, o, key, &ptr, os_type_INTEGER, NULL); + *val = (int) ptr; + + return ret; +} + +int os_object_get_bool(os_t os, os_object_t o, const char *key, int *val) { + void *ptr = (void *) val; + int ret; + + ret = os_object_get(os, o, key, &ptr, os_type_INTEGER, NULL); + *val = (int) ptr; + + return ret; +} + +int os_object_get_time(os_t os, os_object_t o, const char *key, time_t *val) { + void *ptr = (void *) val; + int ret; + + ret = os_object_get(os, o, key, &ptr, os_type_INTEGER, NULL); + *val = (time_t) ptr; + + return ret; +} + +int os_object_get(os_t os, os_object_t o, const char *key, void **val, os_type_t type, os_type_t *ot) { + os_field_t osf; + nad_t nad; + + /* Type complexity is to deal with string/NADs. If an object contains xml, it will only be + parsed and returned as a NAD if type == os_type_NAD, otherwise if type == os_type_UNKNOWN + it will be returned as string, unless it's already been converted to a NAD */ + + osf = (os_field_t) xhash_get(o->hash, key); + if(osf == NULL) { + *val = NULL; + return 0; + } + + if (ot != NULL) + *ot = osf->type; + + if (type == os_type_UNKNOWN) + type = osf->type; + + switch(type) { + case os_type_BOOLEAN: + case os_type_INTEGER: + * (int *) val = * (int *) osf->val; + break; + + case os_type_STRING: + *val = osf->val; + break; + + case os_type_NAD: + /* check to see whether it's already a NAD */ + if (osf->type == os_type_NAD) { + *val = osf->val; + } else { + /* parse the string into a NAD */ + nad = nad_parse(NULL, ((char *) osf->val) + 3, strlen(osf->val) - 3); + if(nad == NULL) { + /* unparseable NAD */ + log_debug(ZONE, "cell returned from storage for key %s has unparseable XML content (%lu bytes)", key, strlen(osf->val)-3); + return 0; + } + + /* replace the string with a NAD */ + osf->val = (void *) nad; + + pool_cleanup(os->p, (pool_cleaner) nad_free, (void *) nad); + + *val = osf->val; + osf->type = os_type_NAD; + + } + break; + + default: + *val = NULL; + } + + log_debug(ZONE, "got field %s (val %x type %d) to object", key, *val, type); + + return 1; +} + +int os_object_iter_first(os_object_t o) { + return xhash_iter_first(o->hash); +} + +int os_object_iter_next(os_object_t o) { + return xhash_iter_next(o->hash); +} + +void os_object_iter_get(os_object_t o, char **key, void **val, os_type_t *type) { + os_field_t osf; + union xhashv xhv; + + xhv.osf_val = &osf; + xhash_iter_get(o->hash, (const char **) key, xhv.val); + + if(*key == NULL) { + *val = NULL; + return; + } + + *type = osf->type; + + switch(osf->type) { + case os_type_BOOLEAN: + case os_type_INTEGER: + * (int *) val = * (int *) osf->val; + break; + + case os_type_STRING: + case os_type_NAD: + *val = osf->val; + break; + + default: + *val = NULL; + } + + log_debug(ZONE, "got iter field %s (val %x type %d) to object", *key, *val, *type); +} diff --git a/sm/pkt.c b/sm/pkt.c new file mode 100644 index 00000000..30ab6012 --- /dev/null +++ b/sm/pkt.c @@ -0,0 +1,493 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/pkt.c + * @brief packet abstraction + * @author Robert Norris + * $Date: 2005/09/09 05:34:13 $ + * $Revision: 1.35 $ + */ + +pkt_t pkt_error(pkt_t pkt, int err) { + if(pkt == NULL) return NULL; + + /* if it's an error already, log, free, return */ + if(pkt->type & pkt_ERROR) { + log_debug(ZONE, "dropping error pkt"); + pkt_free(pkt); + return NULL; + } + + stanza_error(pkt->nad, 1, err); + + /* update vars and attrs */ + pkt_tofrom(pkt); + pkt->type |= pkt_ERROR; + + /* all done, error'd and addressed */ + log_debug(ZONE, "processed %d error pkt", err); + + return pkt; +} + +/** swap a packet's to and from attributes */ +pkt_t pkt_tofrom(pkt_t pkt) { + jid_t tmp; + + if(pkt == NULL) return NULL; + + /* swap vars */ + tmp = pkt->from; + pkt->from = pkt->to; + pkt->to = tmp; + + /* update attrs */ + if(pkt->to != NULL) + nad_set_attr(pkt->nad, 1, -1, "to", jid_full(pkt->to), 0); + if(pkt->from != NULL) + nad_set_attr(pkt->nad, 1, -1, "from", jid_full(pkt->from), 0); + + return pkt; +} + +/** duplicate pkt, replacing addresses */ +pkt_t pkt_dup(pkt_t pkt, const char *to, const char *from) { + pkt_t pnew; + + if(pkt == NULL) return NULL; + + pnew = (pkt_t) malloc(sizeof(struct pkt_st)); + memset(pnew, 0, sizeof(struct pkt_st)); + + pnew->sm = pkt->sm; + pnew->type = pkt->type; + pnew->nad = nad_copy(pkt->nad); + + /* set replacement attrs */ + if(to != NULL) { + pnew->to = jid_new(pkt->sm->pc, to, -1); + nad_set_attr(pnew->nad, 1, -1, "to", jid_full(pnew->to), 0); + } else if(pkt->to != NULL) + pnew->to = jid_dup(pkt->to); + + if(from != NULL) { + pnew->from = jid_new(pkt->sm->pc, from, -1); + nad_set_attr(pnew->nad, 1, -1, "from", jid_full(pnew->from), 0); + } else if(pkt->from != NULL) + pnew->from = jid_dup(pkt->from); + + log_debug(ZONE, "duplicated packet"); + + return pnew; +} + +pkt_t pkt_new(sm_t sm, nad_t nad) { + pkt_t pkt; + int ns, attr, elem; + char pri[20]; + + log_debug(ZONE, "creating new packet"); + + /* find the route */ + ns = nad_find_namespace(nad, 0, uri_COMPONENT, NULL); + if(ns < 0) { + log_debug(ZONE, "packet not in component namespace"); + return NULL; + } + + /* create the pkt holder */ + pkt = (pkt_t) malloc(sizeof(struct pkt_st)); + memset(pkt, 0, sizeof(struct pkt_st)); + + pkt->sm = sm; + pkt->nad = nad; + + /* routes */ + if(NAD_ENAME_L(nad, 0) == 5 && strncmp("route", NAD_ENAME(nad, 0), 5) == 0) { + /* route element */ + if((attr = nad_find_attr(nad, 0, -1, "to", NULL)) >= 0) + pkt->rto = jid_new(sm->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + if((attr = nad_find_attr(nad, 0, -1, "from", NULL)) >= 0) + pkt->rfrom = jid_new(sm->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + /* route type */ + attr = nad_find_attr(nad, 0, -1, "type", NULL); + if(attr < 0) + pkt->rtype = route_UNICAST; + else if(NAD_AVAL_L(nad, attr) == 9 && strncmp("broadcast", NAD_AVAL(nad, attr), 9) == 0) + pkt->rtype = route_BROADCAST; + + /* route errors */ + if(nad_find_attr(nad, 0, -1, "error", NULL) >= 0) + pkt->rtype |= route_ERROR; + + /* client packets */ + ns = nad_find_namespace(nad, 1, uri_CLIENT, NULL); + if(ns >= 0) { + + /* get initial addresses */ + if((attr = nad_find_attr(pkt->nad, 1, -1, "to", NULL)) >= 0 && NAD_AVAL_L(pkt->nad, attr) > 0) + pkt->to = jid_new(sm->pc, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + if((attr = nad_find_attr(pkt->nad, 1, -1, "from", NULL)) >= 0 && NAD_AVAL_L(pkt->nad, attr) > 0) + pkt->from = jid_new(sm->pc, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)); + + /* find type, if any */ + attr = nad_find_attr(pkt->nad, 1, -1, "type", NULL); + + /* messages are simple */ + if(NAD_ENAME_L(pkt->nad, 1) == 7 && strncmp("message", NAD_ENAME(pkt->nad, 1), 7) == 0) { + pkt->type = pkt_MESSAGE; + if(attr >= 0 && NAD_AVAL_L(pkt->nad, attr) == 5 && strncmp("error", NAD_AVAL(pkt->nad, attr), 5) == 0) + pkt->type |= pkt_ERROR; + return pkt; + } + + /* presence is a mixed bag, s10ns in here too */ + if(NAD_ENAME_L(pkt->nad, 1) == 8 && strncmp("presence", NAD_ENAME(pkt->nad, 1), 8) == 0) { + pkt->type = pkt_PRESENCE; + if(attr >= 0) { + if(NAD_AVAL_L(pkt->nad, attr) == 11 && strncmp("unavailable", NAD_AVAL(pkt->nad, attr), 11) == 0) + pkt->type = pkt_PRESENCE_UN; + else if(NAD_AVAL_L(pkt->nad, attr) == 5 && strncmp("probe", NAD_AVAL(pkt->nad, attr), 5) == 0) + pkt->type = pkt_PRESENCE_PROBE; + else if(NAD_AVAL_L(pkt->nad, attr) == 9 && strncmp("invisible", NAD_AVAL(pkt->nad, attr), 9) == 0) + pkt->type = pkt_PRESENCE_INVIS; + else if(NAD_AVAL_L(pkt->nad, attr) == 9 && strncmp("subscribe", NAD_AVAL(pkt->nad, attr), 9) == 0) + pkt->type = pkt_S10N; + else if(NAD_AVAL_L(pkt->nad, attr) == 10 && strncmp("subscribed", NAD_AVAL(pkt->nad, attr), 10) == 0) + pkt->type = pkt_S10N_ED; + else if(NAD_AVAL_L(pkt->nad, attr) == 11 && strncmp("unsubscribe", NAD_AVAL(pkt->nad, attr), 11) == 0) + pkt->type = pkt_S10N_UN; + else if(NAD_AVAL_L(pkt->nad, attr) == 12 && strncmp("unsubscribed", NAD_AVAL(pkt->nad, attr), 12) == 0) + pkt->type = pkt_S10N_UNED; + else if(NAD_AVAL_L(pkt->nad, attr) == 5 && strncmp("error", NAD_AVAL(pkt->nad, attr), 5) == 0) + pkt->type = pkt_PRESENCE | pkt_ERROR; + } + + /* priority */ + if((elem = nad_find_elem(pkt->nad, 1, NAD_ENS(pkt->nad, 1), "priority", 1)) < 0) + return pkt; + + if(NAD_CDATA_L(pkt->nad, elem) <= 0 || NAD_CDATA_L(pkt->nad, elem) > 19) + return pkt; + + memcpy(pri, NAD_CDATA(pkt->nad, elem), NAD_CDATA_L(pkt->nad, elem)); + pri[NAD_CDATA_L(pkt->nad, elem)] = '\0'; + pkt->pri = atoi(pri); + + if(pkt->pri > 127) pkt->pri = 127; + if(pkt->pri < -128) pkt->pri = -128; + + return pkt; + } + + /* iq's are pretty easy, but also set xmlns */ + if(NAD_ENAME_L(pkt->nad, 1) == 2 && strncmp("iq", NAD_ENAME(pkt->nad, 1), 2) == 0) { + pkt->type = pkt_IQ; + if(attr >= 0) { + if(NAD_AVAL_L(pkt->nad, attr) == 3 && strncmp("set", NAD_AVAL(pkt->nad, attr), 3) == 0) + pkt->type = pkt_IQ_SET; + else if(NAD_AVAL_L(pkt->nad, attr) == 6 && strncmp("result", NAD_AVAL(pkt->nad, attr), 6) == 0) + pkt->type = pkt_IQ_RESULT; + else if(NAD_AVAL_L(pkt->nad, attr) == 5 && strncmp("error", NAD_AVAL(pkt->nad, attr), 5) == 0) + pkt->type = pkt_IQ | pkt_ERROR; + } + + if(pkt->nad->ecur > 2 && (ns = NAD_ENS(pkt->nad, 2)) >= 0) + pkt->ns = (int) xhash_getx(pkt->sm->xmlns, NAD_NURI(pkt->nad, ns), NAD_NURI_L(pkt->nad, ns)); + + return pkt; + } + + log_debug(ZONE, "unknown client namespace packet"); + + return pkt; + } + + /* sessions packets */ + ns = nad_find_namespace(nad, 1, uri_SESSION, NULL); + if(ns >= 0) { + + /* sessions */ + if(NAD_ENAME_L(pkt->nad, 1) == 7 && strncmp("session", NAD_ENAME(pkt->nad, 1), 7) == 0) { + + /* find action */ + attr = nad_find_attr(pkt->nad, 1, -1, "action", NULL); + + if(attr >= 0) { + if(NAD_AVAL_L(pkt->nad, attr) == 5 && strncmp("start", NAD_AVAL(pkt->nad, attr), 5) >= 0) + pkt->type = pkt_SESS; + else if(NAD_AVAL_L(pkt->nad, attr) == 3 && strncmp("end", NAD_AVAL(pkt->nad, attr), 3) >= 0) + pkt->type = pkt_SESS_END; + else if(NAD_AVAL_L(pkt->nad, attr) == 6 && strncmp("create", NAD_AVAL(pkt->nad, attr), 6) >= 0) + pkt->type = pkt_SESS_CREATE; + else if(NAD_AVAL_L(pkt->nad, attr) == 6 && strncmp("delete", NAD_AVAL(pkt->nad, attr), 6) >= 0) + pkt->type = pkt_SESS_DELETE; + else if(NAD_AVAL_L(pkt->nad, attr) == 7 && strncmp("started", NAD_AVAL(pkt->nad, attr), 7) >= 0) + pkt->type = pkt_SESS | pkt_SESS_FAILED; + else if(NAD_AVAL_L(pkt->nad, attr) == 5 && strncmp("ended", NAD_AVAL(pkt->nad, attr), 5) >= 0) + pkt->type = pkt_SESS_END | pkt_SESS_FAILED; + else if(NAD_AVAL_L(pkt->nad, attr) == 7 && strncmp("created", NAD_AVAL(pkt->nad, attr), 7) >= 0) + pkt->type = pkt_SESS_CREATE | pkt_SESS_FAILED; + else if(NAD_AVAL_L(pkt->nad, attr) == 7 && strncmp("deleted", NAD_AVAL(pkt->nad, attr), 7) >= 0) + pkt->type = pkt_SESS_DELETE | pkt_SESS_FAILED; + + return pkt; + } else { + log_debug(ZONE, "missing action on session packet"); + return pkt; + } + } + + log_debug(ZONE, "unknown session namespace packet"); + + return pkt; + } + + log_debug(ZONE, "unknown packet"); + + return pkt; + } + + /* advertisements */ + if(NAD_ENAME_L(nad, 0) == 8 && strncmp("presence", NAD_ENAME(nad, 0), 8) == 0) { + if(nad_find_attr(nad, 0, -1, "type", "unavailable") >= 0) + pkt->rtype = route_ADV_UN; + else + pkt->rtype = route_ADV; + + attr = nad_find_attr(nad, 0, -1, "from", NULL); + if(attr >= 0) + pkt->from = jid_new(sm->pc, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + return pkt; + } + + log_debug(ZONE, "invalid component packet"); + + free(pkt); + return NULL; +} + +void pkt_free(pkt_t pkt) { + log_debug(ZONE, "freeing pkt"); + + if(pkt->rto != NULL) jid_free(pkt->rto); + if(pkt->rfrom != NULL) jid_free(pkt->rfrom); + if(pkt->to != NULL) jid_free(pkt->to); + if(pkt->from != NULL) jid_free(pkt->from); + if(pkt->nad != NULL) nad_free(pkt->nad); + free(pkt); +} + +pkt_t pkt_create(sm_t sm, const char *elem, const char *type, const char *to, const char *from) { + nad_t nad; + int ns; + + nad = nad_new(sm->router->nad_cache); + + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, ns, "route", 0); + + nad_add_namespace(nad, uri_SESSION, "sm"); + + ns = nad_add_namespace(nad, uri_CLIENT, NULL); + nad_append_elem(nad, ns, elem, 1); + + if(type != NULL) + nad_append_attr(nad, -1, "type", type); + if(to != NULL) + nad_append_attr(nad, -1, "to", to); + if(from != NULL) + nad_append_attr(nad, -1, "from", from); + + return pkt_new(sm, nad); +} + +/** convenience - copy the packet id from src to dest */ +void pkt_id(pkt_t src, pkt_t dest) { + int attr; + + attr = nad_find_attr(src->nad, 1, -1, "id", NULL); + if(attr >= 0) + nad_set_attr(dest->nad, 1, -1, "id", NAD_AVAL(src->nad, attr), NAD_AVAL_L(src->nad, attr)); + else + nad_set_attr(dest->nad, 1, -1, "id", NULL, 0); +} + +/** create an id value for new iq packets */ +void pkt_id_new(pkt_t pkt) { + char id[8]; + int i, r; + + /* as we are not using ids for tracking purposes, these can be generated randomly */ + for(i = 0; i < 8; i++) { + r = (int) (36.0 * rand() / RAND_MAX); + id[i] = (r >= 0 && r <= 9) ? (r + 48) : (r + 87); + } + + nad_set_attr(pkt->nad, 1, -1, "id", id, 8); + + return; +} + +void pkt_router(pkt_t pkt) { + mod_ret_t ret; + int ns, scan; + + if(pkt == NULL) return; + + log_debug(ZONE, "delivering pkt to router"); + + if(pkt->to == NULL) { + log_debug(ZONE, "no to address on packet, unable to route"); + pkt_free(pkt); + return; + } + + if(pkt->rto != NULL) + jid_free(pkt->rto); + pkt->rto = jid_new(pkt->sm->pc, pkt->to->domain, -1); + + if(pkt->rto == NULL) { + log_debug(ZONE, "invalid to address on packet, unable to route"); + pkt_free(pkt); + return; + } + + nad_set_attr(pkt->nad, 0, -1, "to", pkt->rto->domain, 0); + + if(pkt->rfrom != NULL) + jid_free(pkt->rfrom); + pkt->rfrom = jid_new(pkt->sm->pc, pkt->sm->id, -1); + + if(pkt->rfrom == NULL) { + log_debug(ZONE, "invalid from address on packet, unable to route"); + pkt_free(pkt); + return; + } + + nad_set_attr(pkt->nad, 0, -1, "from", pkt->rfrom->domain, 0); + + ret = mm_out_router(pkt->sm->mm, pkt); + switch(ret) { + case mod_HANDLED: + return; + + case mod_PASS: + + /* remove sm specifics */ + ns = nad_find_namespace(pkt->nad, 1, uri_SESSION, NULL); + if(ns > 0) { + nad_set_attr(pkt->nad, 1, ns, "c2s", NULL, 0); + nad_set_attr(pkt->nad, 1, ns, "sm", NULL, 0); + + /* forget about the internal namespace too */ + if(pkt->nad->elems[1].ns == ns) + pkt->nad->elems[1].ns = pkt->nad->nss[ns].next; + + else { + for(scan = pkt->nad->elems[1].ns; pkt->nad->nss[scan].next != -1 && pkt->nad->nss[scan].next != ns; scan = pkt->nad->nss[scan].next); + + /* got it */ + if(pkt->nad->nss[scan].next != -1) + pkt->nad->nss[scan].next = pkt->nad->nss[ns].next; + } + } + + sx_nad_write(pkt->sm->router, pkt->nad); + + /* nad already free'd, free the rest */ + pkt->nad = NULL; + pkt_free(pkt); + + break; + + default: + pkt_router(pkt_error(pkt, -ret)); + + break; + } +} + +void pkt_sess(pkt_t pkt, sess_t sess) { + mod_ret_t ret; + + if(pkt == NULL) return; + + log_debug(ZONE, "delivering pkt to session %s", jid_full(sess->jid)); + + if(pkt->rto != NULL) + jid_free(pkt->rto); + pkt->rto = jid_new(pkt->sm->pc, sess->c2s, -1); + + if(pkt->rto == NULL) { + log_debug(ZONE, "invalid to address on packet, unable to route"); + pkt_free(pkt); + return; + } + + nad_set_attr(pkt->nad, 0, -1, "to", pkt->rto->domain, 0); + + if(pkt->rfrom != NULL) + jid_free(pkt->rfrom); + pkt->rfrom = jid_new(pkt->sm->pc, pkt->sm->id, -1); + + if(pkt->rfrom == NULL) { + log_debug(ZONE, "invalid to address on packet, unable to route"); + pkt_free(pkt); + return; + } + + nad_set_attr(pkt->nad, 0, -1, "from", pkt->rfrom->domain, 0); + + ret = mm_out_sess(pkt->sm->mm, sess, pkt); + switch(ret) { + case mod_HANDLED: + return; + + case mod_PASS: + sess_route(sess, pkt); + + break; + + default: + pkt_router(pkt_error(pkt, -ret)); + + break; + } +} + +/** add an x:delay stamp */ +void pkt_delay(pkt_t pkt, time_t t, const char *from) { + char timestamp[18]; + int ns, elem; + + datetime_out(t, dt_LEGACY, timestamp, 18); + ns = nad_add_namespace(pkt->nad, uri_DELAY, NULL); + elem = nad_insert_elem(pkt->nad, 1, ns, "x", NULL); + nad_set_attr(pkt->nad, elem, -1, "stamp", timestamp, 0); + if(from != NULL) + nad_set_attr(pkt->nad, elem, -1, "from", from, 0); + + log_debug(ZONE, "added pkt delay stamp %s", timestamp); +} diff --git a/sm/pres.c b/sm/pres.c new file mode 100644 index 00000000..6e3d50f5 --- /dev/null +++ b/sm/pres.c @@ -0,0 +1,420 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/pres.c + * @brief presence tracker + * @author Robert Norris + * $Date: 2005/06/02 04:48:25 $ + * $Revision: 1.41 $ + */ + +/* + * there are four entry points + * + * pres_update(sess, pkt) - presence updates from a session (T1, T2, T3) + * pres_in(user, pkt) - presence updates from a remote jid (T4, T5) + * pres_error(sess, jid) - remote jid bounced an update (T6) + * pres_deliver(sess, pkt) - outgoing directed presence (T7, T8) + */ + +/** select a new top session based on current session presence */ +void _pres_top(user_t user) { + sess_t scan; + + user->top = NULL; + + /* loop the active sessions */ + for(scan = user->sessions; scan != NULL; scan = scan->next) { + /* non available and/or negative presence can't become top session */ + if(!scan->available || scan->pri < 0) continue; + + /* if we don't have one, then this is it */ + if(user->top == NULL) + user->top = scan; + + /* if we have higher priority than current top, we're the new top */ + if(scan->pri >= user->top->pri) + user->top = scan; + } + + if(user->top == NULL) { + log_debug(ZONE, "no priority >= 0 sessions, so no top session"); + } else { + log_debug(ZONE, "top session for %s is now %s (priority %d)", jid_user(user->jid), jid_full(user->top->jid), user->top->pri); + } +} + +/** presence updates from a session */ +void pres_update(sess_t sess, pkt_t pkt) { + item_t item; + int self; + jid_t scan, next; + sess_t sscan; + user_t user; + int user_is_local; + int user_connected = 0; + + switch(pkt->type) { + case pkt_PRESENCE: + log_debug(ZONE, "available presence for session %s", jid_full(sess->jid)); + + /* cache packet for later */ + if(sess->pres != NULL) + pkt_free(sess->pres); + sess->pres = pkt; + + /* B1: forward to all in T, unless in E */ + + /* loop the roster, looking for trusted */ + self = 0; + if(xhash_iter_first(sess->user->roster)) + do { + xhash_iter_get(sess->user->roster, NULL, (void *) &item); + + /* Is the user local ? */ + user_is_local = (strcmp(pkt->sm->id, item->jid->domain)==0); + if (user_is_local) { + user = xhash_get(pkt->sm->users, jid_user(item->jid)); + user_connected = ((user!=NULL) && (user->sessions != NULL)); + } + + /* if we're coming available, and we can see them, we need to probe them */ + if(!sess->available && item->to) { + + /* Shortcut */ + if ((!user_is_local) || (user_is_local && user_connected)) { + log_debug(ZONE, "probing %s", jid_full(item->jid)); + pkt_router(pkt_create(sess->user->sm, "presence", "probe", jid_full(item->jid), jid_user(sess->jid))); + } else + log_debug(ZONE, "skipping probe to local user %s - not connected", jid_full(item->jid)); + + /* flag if we probed ourselves */ + if(strcmp(jid_user(sess->jid), jid_full(item->jid)) == 0) + self = 1; + } + + /* if they can see us, forward */ + if(item->from && !jid_search(sess->E, item->jid)) { + /* Shortcut: if the domain of this user's jid is the same as this sm, + and the user has no active sessions, don't send presence update */ + + if ((!user_is_local) || (user_is_local && user_connected)) { + log_debug(ZONE, "forwarding available to %s", jid_full(item->jid)); + pkt_router(pkt_dup(pkt, jid_full(item->jid), jid_full(sess->jid))); + } else + log_debug(ZONE, "skipping forwarding available to %s - not connected", jid_full(item->jid)); + } + } while(xhash_iter_next(sess->user->roster)); + + /* probe ourselves if we need to and didn't already */ + if(!self && !sess->available) { + log_debug(ZONE, "probing ourselves"); + pkt_router(pkt_create(sess->user->sm, "presence", "probe", jid_user(sess->jid), jid_user(sess->jid))); + } + + /* forward to our active sessions */ + for(sscan = sess->user->sessions; sscan != NULL; sscan = sscan->next) { + if(sscan != sess && sscan->available) { + log_debug(ZONE, "forwarding available to our session %s", jid_full(sscan->jid)); + pkt_router(pkt_dup(pkt, jid_full(sscan->jid), jid_full(sess->jid))); + } + } + + /* update vars */ + sess->available = 1; + sess->invisible = 0; + + /* new priority */ + sess->pri = pkt->pri; + + /* stamp the saved presence so future probes know how old it is */ + pkt_delay(pkt, time(NULL), jid_full(pkt->from)); + + break; + + case pkt_PRESENCE_UN: + log_debug(ZONE, "unavailable presence for session %s", jid_full(sess->jid)); + + /* free cached presence */ + if(sess->pres != NULL) { + pkt_free(sess->pres); + sess->pres = NULL; + } + + /* B2: forward to all in T and A, unless in E */ + + /* loop the roster, looking for trusted */ + if(xhash_iter_first(sess->user->roster)) + do { + xhash_iter_get(sess->user->roster, NULL, (void *) &item); + + /* Is the user local ? */ + user_is_local = (strcmp(pkt->sm->id, item->jid->domain)==0); + if (user_is_local) { + user = xhash_get(pkt->sm->users, jid_user(item->jid)); + user_connected = ((user!=NULL) && (user->sessions != NULL)); + } + + /* forward if they're trusted and they're not E */ + if(item->from && !jid_search(sess->E, item->jid)) { + + /* Shortcut: same technique as for presence available above */ + if (!user_is_local || (user_is_local && user_connected)) { + log_debug(ZONE, "forwarding unavailable to %s", jid_full(item->jid)); + pkt_router(pkt_dup(pkt, jid_full(item->jid), jid_full(sess->jid))); + } else + log_debug(ZONE, "skipping forwarding unavailable to %s - not connected", jid_full(item->jid)); + } + } while(xhash_iter_next(sess->user->roster)); + + /* walk A and forward to untrusted */ + for(scan = sess->A; scan != NULL; scan = scan->next) + if(!pres_trust(sess->user, scan)) { + log_debug(ZONE, "forwarding unavailable to %s", jid_full(scan)); + pkt_router(pkt_dup(pkt, jid_full(scan), jid_full(sess->jid))); + } + + /* forward to our active sessions */ + for(sscan = sess->user->sessions; sscan != NULL; sscan = sscan->next) { + if(sscan != sess && sscan->available) { + log_debug(ZONE, "forwarding available to our session %s", jid_full(sscan->jid)); + pkt_router(pkt_dup(pkt, jid_full(sscan->jid), jid_full(sess->jid))); + } + } + + /* drop A, E */ + scan = sess->A; + while(scan != NULL) { + next = scan->next; + jid_free(scan); + scan = next; + } + sess->A = NULL; + + scan = sess->E; + while(scan != NULL) { + next = scan->next; + jid_free(scan); + scan = next; + } + sess->E = NULL; + + /* update vars */ + sess->available = 0; + sess->invisible = 0; + + /* done */ + pkt_free(pkt); + + break; + + case pkt_PRESENCE_INVIS: + log_debug(ZONE, "invisible presence for session %s", jid_full(sess->jid)); + + /* only process if we're not already invisible */ + if(!sess->invisible) { + /* B3: forward to all in T, unless in A or E */ + + /* loop the roster, looking for trusted */ + if(xhash_iter_first(sess->user->roster)) + do { + xhash_iter_get(sess->user->roster, NULL, (void *) &item); + + /* if they can see us, and we haven't sent directed to them, then tell them we're gone */ + if(item->from && !jid_search(sess->A, item->jid) && !jid_search(sess->E, item->jid)) { + log_debug(ZONE, "sending unavailable (invisible) to %s", jid_full(item->jid)); + pkt_router(pkt_create(sess->user->sm, "presence", "unavailable", jid_full(item->jid), jid_full(sess->jid))); + } + } while(xhash_iter_next(sess->user->roster)); + + /* forward to our active sessions */ + for(sscan = sess->user->sessions; sscan != NULL; sscan = sscan->next) { + if(sscan != sess && sscan->available) { + log_debug(ZONE, "sending unavailable (invisible) to our session %s", jid_full(sscan->jid)); + pkt_router(pkt_create(sess->user->sm, "presence", "unavailable", jid_full(sscan->jid), jid_full(sess->jid))); + } + } + } + + /* update vars */ + sess->invisible = 1; + + /* done */ + pkt_free(pkt); + + break; + + default: + log_debug(ZONE, "pres_update got packet type %d, this shouldn't happen", pkt->type); + pkt_free(pkt); + return; + } + + /* reset the top session */ + _pres_top(sess->user); +} + +/** presence updates from a remote jid */ +void pres_in(user_t user, pkt_t pkt) { + sess_t scan; + + log_debug(ZONE, "type %d presence packet from %s", pkt->type, jid_full(pkt->from)); + + /* loop over each session */ + for(scan = user->sessions; scan != NULL; scan = scan->next) { + /* don't deliver to unavailable sessions: B5(a) */ + if(!scan->available) + continue; + + /* don't deliver to ourselves, lest we presence-bomb ourselves ;) */ + if(jid_compare_full(pkt->from, scan->jid) == 0) + continue; + + /* handle probes */ + if(pkt->type == pkt_PRESENCE_PROBE) { + log_debug(ZONE, "probe from %s for %s", jid_full(pkt->from), jid_full(scan->jid)); + + /* B4(a): respond if in T and I clear */ + if(!scan->invisible && pres_trust(user, pkt->from)) { + log_debug(ZONE, "responding with last presence update"); + pkt_router(pkt_dup(scan->pres, jid_full(pkt->from), jid_full(scan->jid))); + } + + /* B4(b): respond if in T and in A and I set */ + else if(scan->invisible && pres_trust(user, pkt->from) && jid_search(scan->A, pkt->from)) { + log_debug(ZONE, "we're invisible, responding with raw available"); + pkt_router(pkt_create(user->sm, "presence", NULL, jid_full(pkt->from), jid_full(scan->jid))); + } + + else { + log_debug(ZONE, "probe not authorised, ignoring"); + } + + /* remove from E */ + scan->E = jid_zap(scan->E, pkt->from); + + continue; + } + + /* deliver to session: B5(b) */ + log_debug(ZONE, "forwarding to %s", jid_full(scan->jid)); + pkt_sess(pkt_dup(pkt, jid_full(scan->jid), jid_full(pkt->from)), scan); + } + + pkt_free(pkt); +} + +void pres_error(sess_t sess, jid_t jid) { + /* bounced updates: B6: add to E, remove from A */ + log_debug(ZONE, "bounced presence from %s, adding to error list", jid_full(jid)); + sess->E = jid_append(sess->E, jid); + sess->A = jid_zap(sess->A, jid); +} + +/** outgoing directed presence */ +void pres_deliver(sess_t sess, pkt_t pkt) { + + if(jid_full(pkt->to) == NULL) { + log_debug(ZONE, "invalid jid in directed presence packet"); + pkt_free(pkt); + return; + } + + if(pkt->type == pkt_PRESENCE) { + /* B7: forward, add to A (unless in T), remove from E */ + log_debug(ZONE, "delivering directed available presence to %s", jid_full(pkt->to)); + if(!pres_trust(sess->user, pkt->to)) + sess->A = jid_append(sess->A, pkt->to); + sess->E = jid_zap(sess->E, pkt->to); + pkt_router(pkt); + return; + } + + if(pkt->type == pkt_PRESENCE_UN) { + /* B8: forward, remove from A and E */ + log_debug(ZONE, "delivering directed unavailable presence to %s", jid_full(pkt->to)); + sess->A = jid_zap(sess->A, pkt->to); + sess->E = jid_zap(sess->E, pkt->to); + pkt_router(pkt); + return; + } + + log_debug(ZONE, "don't know how to deliver presence type %d to %s, dropping", pkt->type, jid_full(pkt->to)); + + pkt_free(pkt); +} + +/** see if the jid is trusted (ie in the roster with s10n="from" or "both") */ +int pres_trust(user_t user, jid_t jid) { + item_t item; + + /* trusted if they're in the roster and they can see us */ + item = xhash_get(user->roster, jid_user(jid)); + if(item != NULL && item->from) + return 1; + + /* always trust ourselves */ + if(jid_compare_user(user->jid, jid) == 0) + return 1; + + return 0; +} + +/** send presence based on roster changes */ +void pres_roster(sess_t sess, item_t item) { + /* if we're not available, then forget it */ + if(!sess->available) + return; + + /* if they were trusted previously, but aren't anymore, and we haven't + * explicitly sent them presence, then make them forget */ + if(!item->from && !jid_search(sess->A, item->jid) && !jid_search(sess->E, item->jid)) { + log_debug(ZONE, "forcing unavailable to %s after roster change", jid_full(item->jid)); + pkt_router(pkt_create(sess->user->sm, "presence", "unavailable", jid_full(item->jid), jid_full(sess->jid))); + return; + } + + /* if they're now trusted, and we're not invisible, and we haven't sent + * them directed presence, then they get to see us for the first time */ + if(item->from && !sess->invisible && !jid_search(sess->A, item->jid) && !jid_search(sess->E, item->jid)) { + log_debug(ZONE, "forcing available to %s after roster change", jid_full(item->jid)); + pkt_router(pkt_dup(sess->pres, jid_full(item->jid), jid_full(sess->jid))); + } +} + +void pres_probe(user_t user) { + item_t item; + + log_debug(ZONE, "full roster probe for %s", jid_user(user->jid)); + + /* loop the roster, looked for trusted */ + if(xhash_iter_first(user->roster)) + do { + xhash_iter_get(user->roster, NULL, (void *) &item); + + /* don't probe unless they trust us */ + if(item->to) { + log_debug(ZONE, "probing %s", jid_full(item->jid)); + pkt_router(pkt_create(user->sm, "presence", "probe", jid_full(item->jid), jid_user(user->jid))); + } + } while(xhash_iter_next(user->roster)); +} diff --git a/sm/sess.c b/sm/sess.c new file mode 100644 index 00000000..34289c2f --- /dev/null +++ b/sm/sess.c @@ -0,0 +1,204 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/sess.c + * @brief session management + * @author Robert Norris + * $Date: 2005/07/25 20:38:06 $ + * $Revision: 1.37 $ + */ + +/** send a packet to the client for this session */ +void sess_route(sess_t sess, pkt_t pkt) { + int ns; + + log_debug(ZONE, "routing pkt 0x%X to %s (%s) for %s", pkt, sess->c2s, sess->c2s_id, jid_full(sess->jid)); + + if(pkt == NULL) + return; + + /* wrap it up */ + ns = nad_append_namespace(pkt->nad, 1, uri_SESSION, "sm"); + + nad_set_attr(pkt->nad, 1, ns, "c2s", sess->c2s_id, 0); + nad_set_attr(pkt->nad, 1, ns, "sm", sess->sm_id, 0); + + nad_set_attr(pkt->nad, 0, -1, "to", sess->c2s, 0); + nad_set_attr(pkt->nad, 0, -1, "from", sess->user->sm->id, 0); + + /* and send it out */ + sx_nad_write(sess->user->sm->router, pkt->nad); + + /* free up the packet */ + if(pkt->rto != NULL) jid_free(pkt->rto); + if(pkt->rfrom != NULL) jid_free(pkt->rfrom); + if(pkt->to != NULL) jid_free(pkt->to); + if(pkt->from != NULL) jid_free(pkt->from); + free(pkt); +} + +static void _sess_end_guts(sess_t sess) { + sess_t scan; + + /* fake an unavailable presence from this session, so that modules and externals know we're gone */ + if(sess->available || sess->invisible || sess->A != NULL) + mm_in_sess(sess->user->sm->mm, sess, pkt_create(sess->user->sm, "presence", "unavailable", NULL, NULL)); + + /* inform the modules */ + mm_sess_end(sess->user->sm->mm, sess); + + /* unlink it from this users sessions */ + if(sess->user->sessions == sess) + sess->user->sessions = sess->next; + else { + for(scan = sess->user->sessions; scan != NULL && scan->next != sess; scan = scan->next); + if(scan != NULL) + scan->next = sess->next; + } + + /* and from global sessions */ + xhash_zap(sess->user->sm->sessions, sess->sm_id); +} + +void sess_end(sess_t sess) { + log_debug(ZONE, "shutting down session %s", jid_full(sess->jid)); + + _sess_end_guts(sess); + + log_write(sess->user->sm->log, LOG_NOTICE, "session ended: jid=%s", jid_full(sess->jid)); + + /* if it was the last session, free the user */ + if(sess->user->sessions == NULL) + user_free(sess->user); + + /* free the session */ + pool_free(sess->p); +} + +sess_t sess_start(sm_t sm, jid_t jid) { + pool p; + user_t user; + sess_t sess, scan; + sha1_state_t sha1; + char hash[20]; + int replaced = 0; + + log_debug(ZONE, "session requested for %s", jid_full(jid)); + + /* get user data for this guy */ + user = user_load(sm, jid); + + /* unknown user */ + if(user == NULL) { + if(config_get(sm->config, "user.auto-create") == NULL) { + log_write(sm->log, LOG_NOTICE, "user not found, can't start session: jid=%s", jid_full(jid)); + return NULL; + } + + log_debug(ZONE, "auto-creating user %s", jid_user(jid)); + + if(user_create(sm, jid) != 0) + return NULL; + + user = user_load(sm, jid); + if(user == NULL) { + log_write(sm->log, LOG_NOTICE, "couldn't load user, can't start session: jid=%s", jid_full(jid)); + return NULL; + } + } + + /* kill their old session if they have one */ + for(scan = user->sessions; scan != NULL; scan = scan->next) + if(jid_compare_full(scan->jid, jid) == 0) { + log_debug(ZONE, "replacing session %s (%s)", jid_full(jid), scan->c2s_id); + + /* !!! this "replaced" stuff is a hack - its really a subaction of "ended". + * hurrah, another control protocol rewrite is needed :( + */ + sm_c2s_action(scan, "replaced", NULL); + + _sess_end_guts(scan); + + pool_free(scan->p); + + replaced = 1; + + break; + } + + /* make a new session */ + p = pool_new(); + + sess = (sess_t) pmalloco(p, sizeof(struct sess_st)); + sess->p = p; + + /* fill it out */ + sess->pri = 0; + sess->user = user; + + sess->jid = jid_dup(jid); + pool_cleanup(sess->p, (void (*))(void *) jid_free, sess->jid); + + /* a place for modules to store stuff */ + sess->module_data = (void **) pmalloco(sess->p, sizeof(void *) * sess->user->sm->mm->nindex); + + /* add it to the list */ + sess->next = user->sessions; + user->sessions = sess; + + /* who c2s should address things to */ + sha1_init(&sha1); + datetime_out(time(NULL), dt_DATETIME, sess->sm_id, 41); + sha1_append(&sha1, sess->sm_id, strlen(sess->sm_id)); + sha1_append(&sha1, jid_full(sess->jid), strlen(jid_full(sess->jid))); + sha1_finish(&sha1, hash); + hex_from_raw(hash, 20, sess->sm_id); + + log_debug(ZONE, "smid is %s", sess->sm_id); + + /* remember it */ + xhash_put(sm->sessions, sess->sm_id, sess); + + /* inform the modules */ + /* !!! catch the return value - if its 1, don't let them in */ + mm_sess_start(sm->mm, sess); + + if(replaced) + log_write(sm->log, LOG_NOTICE, "session replaced: jid=%s", jid_full(sess->jid)); + else + log_write(sm->log, LOG_NOTICE, "session started: jid=%s", jid_full(sess->jid)); + + return sess; +} + +/** match a session by resource */ +sess_t sess_match(user_t user, char *resource) { + sess_t sess; + + for(sess = user->sessions; sess != NULL; sess = sess->next) { + /* exact matches */ + if(strcmp(sess->jid->resource, resource) == 0) + return sess; + } + + return NULL; +} diff --git a/sm/sm.c b/sm/sm.c new file mode 100644 index 00000000..d5717dc4 --- /dev/null +++ b/sm/sm.c @@ -0,0 +1,348 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/sm.c + * @brief stream / io callbacks + * @author Robert Norris + * $Date: 2005/08/17 07:48:28 $ + * $Revision: 1.51 $ + */ + +sig_atomic_t sm_lost_router = 0; + +/** our master callback */ +int sm_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + sm_t sm = (sm_t) arg; + sx_buf_t buf = (sx_buf_t) data; + sx_error_t *sxe; + nad_t nad; + pkt_t pkt; + int len, ns, elem, attr; + + switch(e) { + case event_WANT_READ: + log_debug(ZONE, "want read"); + mio_read(sm->mio, sm->fd); + break; + + case event_WANT_WRITE: + log_debug(ZONE, "want write"); + mio_write(sm->mio, sm->fd); + break; + + case event_READ: + log_debug(ZONE, "reading from %d", sm->fd); + + /* do the read */ + len = recv(sm->fd, buf->data, buf->len, 0); + + if (len < 0) { + if (errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) { + buf->len = 0; + return 0; + } + + log_write(sm->log, LOG_NOTICE, "[%d] [router] read error: %s (%d)", sm->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + } + + else if (len == 0) { + /* they went away */ + sx_kill(s); + + return -1; + } + + log_debug(ZONE, "read %d bytes", len); + + buf->len = len; + + return len; + + case event_WRITE: + log_debug(ZONE, "writing to %d", sm->fd); + + len = send(sm->fd, buf->data, buf->len, 0); + if (len >= 0) { + log_debug(ZONE, "%d bytes written", len); + return len; + } + + if (errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) + return 0; + + log_write(sm->log, LOG_NOTICE, "[%d] [router] write error: %s (%d)", sm->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + + case event_ERROR: + sxe = (sx_error_t *) data; + log_write(sm->log, LOG_NOTICE, "error from router: %s (%s)", sxe->generic, sxe->specific); + + if(sxe->code == SX_ERR_AUTH) + sx_close(s); + + break; + + case event_STREAM: + break; + + case event_OPEN: + log_write(sm->log, LOG_NOTICE, "connection to router established"); + + /* reset connection attempts counter */ + sm->retry_left = sm->retry_init; + + nad = nad_new(sm->router->nad_cache); + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, ns, "bind", 0); + nad_append_attr(nad, -1, "name", sm->id); + + log_debug(ZONE, "requesting component bind for '%s'", sm->id); + + sx_nad_write(sm->router, nad); + + break; + + case event_PACKET: + nad = (nad_t) data; + + /* drop unqualified packets */ + if (NAD_ENS(nad, 0) < 0) { + nad_free(nad); + return 0; + } + /* watch for the features packet */ + if (s->state == state_STREAM) { + if (NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_STREAMS) + || strncmp(uri_STREAMS, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_STREAMS)) != 0 + || NAD_ENAME_L(nad, 0) != 8 || strncmp("features", NAD_ENAME(nad, 0), 8) != 0) { + log_debug(ZONE, "got a non-features packet on an unauth'd stream, dropping"); + nad_free(nad); + return 0; + } + +#ifdef HAVE_SSL + /* starttls if we can */ + if (sm->sx_ssl != NULL && s->ssf == 0) { + ns = nad_find_scoped_namespace(nad, uri_TLS, NULL); + if (ns >= 0) { + elem = nad_find_elem(nad, 0, ns, "starttls", 1); + if (elem >= 0) { + if (sx_ssl_client_starttls(sm->sx_ssl, s, NULL) == 0) { + nad_free(nad); + return 0; + } + log_write(sm->log, LOG_NOTICE, "unable to establish encrypted session with router"); + } + } + } +#endif + + /* !!! pull the list of mechanisms, and choose the best one. + * if there isn't an appropriate one, error and bail */ + + /* authenticate */ + sx_sasl_auth(sm->sx_sasl, s, "jabberd-router", "DIGEST-MD5", sm->router_user, sm->router_pass); + + nad_free(nad); + return 0; + } + + /* watch for the bind response */ + if (s->state == state_OPEN && !sm->online) { + if (NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_COMPONENT) + || strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) != 0 + || NAD_ENAME_L(nad, 0) != 4 || strncmp("bind", NAD_ENAME(nad, 0), 4)) { + log_debug(ZONE, "got a packet from router, but we're not online, dropping"); + nad_free(nad); + return 0; + } + + /* catch errors */ + attr = nad_find_attr(nad, 0, -1, "error", NULL); + if(attr >= 0) { + log_write(sm->log, LOG_NOTICE, "router refused bind request (%.*s)", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + exit(1); + } + + log_debug(ZONE, "coming online"); + + /* we're online */ + sm->online = sm->started = 1; + log_write(sm->log, LOG_NOTICE, "ready for sessions", sm->id); + + nad_free(nad); + return 0; + } + + log_debug(ZONE, "got a packet"); + + pkt = pkt_new(sm, (nad_t) data); + if (pkt == NULL) { + log_debug(ZONE, "invalid packet, dropping"); + + nad_free(nad); + return 0; + } + + /* go */ + dispatch(sm, pkt); + + return 0; + + case event_CLOSED: + mio_close(sm->mio, sm->fd); + break; + } + + return 0; +} + +int sm_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) { + sm_t sm = (sm_t) arg; + int nbytes; + + switch (a) { + case action_READ: + log_debug(ZONE, "read action on fd %d", fd); + + ioctl(fd, FIONREAD, &nbytes); + if(nbytes == 0) { + sx_kill(sm->router); + return 0; + } + + return sx_can_read(sm->router); + + case action_WRITE: + log_debug(ZONE, "write action on fd %d", fd); + return sx_can_write(sm->router); + + case action_CLOSE: + log_debug(ZONE, "close action on fd %d", fd); + log_write(sm->log, LOG_NOTICE, "connection to router closed"); + + sm_lost_router = 1; + + /* we're offline */ + sm->online = 0; + + break; + + case action_ACCEPT: + break; + } + + return 0; +} + +/** this was jutil_timestamp in a former life */ +void sm_timestamp(time_t t, char timestamp[18]) +{ + struct tm *gm = gmtime(&t); + + snprintf(timestamp, 18, "%d%02d%02dT%02d:%02d:%02d", 1900 + gm->tm_year, + gm->tm_mon + 1, gm->tm_mday, gm->tm_hour, gm->tm_min, gm->tm_sec); + + return; +} + +/** send a new action route */ +void sm_c2s_action(sess_t dest, char *action, char *target) { + nad_t nad; + int rns, sns; + + nad = nad_new(dest->user->sm->router->nad_cache); + + rns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, rns, "route", 0); + + nad_append_attr(nad, -1, "to", dest->c2s); + nad_append_attr(nad, -1, "from", dest->user->sm->id); + + sns = nad_add_namespace(nad, uri_SESSION, "sc"); + nad_append_elem(nad, sns, "session", 1); + + if (dest->c2s_id[0] != '\0') + nad_append_attr(nad, sns, "c2s", dest->c2s_id); + if (dest->sm_id[0] != '\0') + nad_append_attr(nad, sns, "sm", dest->sm_id); + + nad_append_attr(nad, -1, "action", action); + if (target != NULL) + nad_append_attr(nad, -1, "target", target); + + log_debug(ZONE, + "routing nad to %s from %s c2s %s s2s %s action %s target %s", + dest->c2s, dest->user->sm->id, dest->c2s_id, dest->sm_id, + action, target); + + sx_nad_write(dest->user->sm->router, nad); +} + +/** this is gratuitous, but apache gets one, so why not? */ +void sm_signature(sm_t sm, char *str) { + if (sm->siglen == 0) { + snprintf(&sm->signature[sm->siglen], 2048 - sm->siglen, "%s", str); + sm->siglen += strlen(str); + } else { + snprintf(&sm->signature[sm->siglen], 2048 - sm->siglen, " %s", str); + sm->siglen += strlen(str) + 1; + } +} + +/** register a new global ns */ +int sm_register_ns(sm_t sm, char *uri) { + int ns_idx; + + ns_idx = (int) xhash_get(sm->xmlns, uri); + if (ns_idx == 0) { + ns_idx = xhash_count(sm->xmlns) + 2; + xhash_put(sm->xmlns, pstrdup(xhash_pool(sm->xmlns), uri), (void *) ns_idx); + } + xhash_put(sm->xmlns_refcount, uri, (void *) ((int) xhash_get(sm->xmlns_refcount, uri) + 1)); + + return ns_idx; +} + +/** unregister a global ns */ +void sm_unregister_ns(sm_t sm, char *uri) { + int refcount = (int) xhash_get(sm->xmlns_refcount, uri); + if (refcount == 1) { + xhash_zap(sm->xmlns, uri); + xhash_zap(sm->xmlns_refcount, uri); + } else if (refcount > 1) { + xhash_put(sm->xmlns_refcount, uri, (void *) ((int) xhash_get(sm->xmlns_refcount, uri) - 1)); + } +} + +/** get a globally registered ns */ +int sm_get_ns(sm_t sm, char *uri) { + return (int) xhash_get(sm->xmlns, uri); +} + diff --git a/sm/sm.h b/sm/sm.h new file mode 100644 index 00000000..d98b29f3 --- /dev/null +++ b/sm/sm.h @@ -0,0 +1,654 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * This program is free software; you can redistribute it and/or drvify + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file sm/sm.h + * @brief data structures and prototypes for the session manager + * @author Jeremie Miller + * @author Robert Norris + * $Date: 2005/09/09 05:34:13 $ + * $Revision: 1.62 $ + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "sx/sx.h" +#include "sx/sasl.h" +#include "sx/ssl.h" +#include "mio/mio.h" +#include "util/util.h" + +#ifdef HAVE_SIGNAL_H + #include +#endif +#ifdef HAVE_SYS_STAT_H + #include +#endif + +#ifdef _MSC_VER + #define DLLEXPORT __declspec(dllexport) +#else + #define DLLEXPORT +#endif + +/* forward declarations */ +typedef struct sm_st *sm_t; +typedef struct user_st *user_t; +typedef struct sess_st *sess_t; +typedef struct aci_st *aci_t; +typedef struct storage_st *storage_t; +typedef struct mm_st *mm_t; + +/* namespace uri strings */ +#define uri_AUTH "jabber:iq:auth" +#define uri_REGISTER "jabber:iq:register" +#define uri_ROSTER "jabber:iq:roster" +#define uri_AGENTS "jabber:iq:agents" +#define uri_DELAY "jabber:x:delay" +#define uri_BROWSE "jabber:iq:browse" +#define uri_EVENT "jabber:x:event" +#define uri_GATEWAY "jabber:iq:gateway" +#define uri_EXPIRE "jabber:x:expire" +#define uri_SEARCH "jabber:iq:search" +#define uri_DISCO "http://jabber.org/protocol/disco" +#define uri_DISCO_ITEMS "http://jabber.org/protocol/disco#items" +#define uri_DISCO_INFO "http://jabber.org/protocol/disco#info" + +/* indexed known namespace values */ +#define ns_AUTH (1) +#define ns_REGISTER (2) +#define ns_ROSTER (3) +#define ns_AGENTS (4) +#define ns_DELAY (5) +#define ns_BROWSE (6) +#define ns_EVENT (7) +#define ns_GATEWAY (8) +#define ns_EXPIRE (9) +#define ns_SEARCH (10) +#define ns_DISCO (11) +#define ns_DISCO_ITEMS (12) +#define ns_DISCO_INFO (13) + +/** packet types */ +typedef enum { + pkt_NONE = 0x00, /**< no packet */ + pkt_MESSAGE = 0x10, /**< message */ + pkt_PRESENCE = 0x20, /**< presence */ + pkt_PRESENCE_UN = 0x21, /**< presence (unavailable) */ + pkt_PRESENCE_INVIS = 0x22, /**< presence (invisible) */ + pkt_PRESENCE_PROBE = 0x24, /**< presence (probe) */ + pkt_S10N = 0x40, /**< subscribe request */ + pkt_S10N_ED = 0x41, /**< subscribed response */ + pkt_S10N_UN = 0x42, /**< unsubscribe request */ + pkt_S10N_UNED = 0x44, /**< unsubscribed response */ + pkt_IQ = 0x80, /**< info/query (get) */ + pkt_IQ_SET = 0x81, /**< info/query (set) */ + pkt_IQ_RESULT = 0x82, /**< info/query (result) */ + pkt_SESS = 0x100, /**< session start request */ + pkt_SESS_END = 0x101, /**< session end request */ + pkt_SESS_CREATE = 0x102, /**< session create request */ + pkt_SESS_DELETE = 0x104, /**< session delete request */ + pkt_SESS_FAILED = 0x08, /**< session request failed (mask) */ + pkt_SESS_MASK = 0x10f, /**< session request (mask) */ + pkt_ERROR = 0x200 /**< packet error */ +} pkt_type_t; + +/** route types */ +typedef enum { + route_NONE = 0x00, /**< no route */ + route_UNICAST = 0x10, /**< unicast */ + route_BROADCAST = 0x11, /**< broadcast */ + route_ADV = 0x20, /**< advertisement (available) */ + route_ADV_UN = 0x21, /**< advertisement (unavailable) */ + route_ERROR = 0x40 /**< route error */ +} route_type_t; + +/** packet summary data wrapper */ +typedef struct pkt_st { + sm_t sm; /**< sm context */ + + sess_t source; /**< session this packet came from */ + + jid_t rto, rfrom; /**< addressing of enclosing route */ + + route_type_t rtype; /**< type of enclosing route */ + + pkt_type_t type; /**< packet type */ + + jid_t to, from; /**< packet addressing (not used for routing) */ + + int ns; /**< iq sub-namespace */ + + int pri; /**< presence priority */ + + nad_t nad; /**< nad of the entire packet */ +} *pkt_t; + +/** roster items */ +typedef struct item_st { + jid_t jid; /**< id of this item */ + + char *name; /**< display name */ + + char **groups; /**< groups this item is in */ + + int ngroups; /**< number of groups in groups array */ + + int to, from; /**< subscription to this item (they get presence FROM us, they send presence TO us) */ + + int ask; /**< pending subscription (0 == none, 1 == subscribe, 2 == unsubscribe) */ +} *item_t; + +/** session manager global context */ +struct sm_st { + char *id; /**< component id (hostname) */ + + char *router_ip; /**< ip to connect to the router at */ + int router_port; /**< port to connect to the router at */ + char *router_user; /**< username to authenticate to the router as */ + char *router_pass; /**< password to authenticate to the router with */ + char *router_pemfile; /**< name of file containing a SSL certificate & + key for channel to the router */ + + mio_t mio; /**< mio context */ + + sx_env_t sx_env; /**< SX environment */ + sx_plugin_t sx_sasl; /**< SX SASL plugin */ + sx_plugin_t sx_ssl; /**< SX SSL plugin */ + + sx_t router; /**< SX of router connection */ + int fd; /**< file descriptor of router connection */ + + xht users; /**< pointers to currently loaded users (key is user@@domain) */ + + xht sessions; /**< pointers to all connected sessions (key is random sm id) */ + + xht xmlns; /**< index of namespaces (for iq sub-namespace in pkt_t) */ + xht xmlns_refcount; /**< ref-counting for modules namespaces */ + + xht features; /**< feature index (key is feature string */ + + config_t config; /**< config context */ + + log_t log; /**< log context */ + + log_type_t log_type; /**< log type */ + char *log_facility; /**< syslog facility (local0 - local7) */ + char *log_ident; /**< log identifier */ + + int retry_init; /**< number of times to try connecting to the router at startup */ + int retry_lost; /**< number of times to try reconnecting to the router if the connection drops */ + int retry_sleep; /**< sleep interval between retries */ + int retry_left; /**< number of tries left before failure */ + + prep_cache_t pc; /**< cache of stringprep'd ids */ + + storage_t st; /**< storage subsystem */ + + mm_t mm; /**< module subsystem */ + + xht acls; /**< access control lists (key is list name, value is jid_t list) */ + + char signature[2048]; /**< server signature */ + int siglen; /**< length of signature */ + + int started; /**< true if we've connected to the router at least once */ + + int online; /**< true if we're currently bound in the router */ +}; + +/** data for a single user */ +struct user_st { + pool p; /**< memory pool this user is allocated off */ + + sm_t sm; /**< sm context */ + + jid_t jid; /**< user jid (user@@host) */ + + xht roster; /**< roster for this user (key is full jid of item, value is item_t) */ + + sess_t sessions; /**< list of action sessions */ + sess_t top; /**< top priority session */ + + time_t active; /**< time that user first logged in (ever) */ + + void **module_data; /**< per-user module data */ +}; + +/** data for a single session */ +struct sess_st { + pool p; /**< memory pool this session is allocated off */ + + user_t user; /**< user this session belongs to */ + + jid_t jid; /**< session jid (user@@host/res) */ + + char c2s[1024]; /**< id of c2s that is handling their connection */ + + char sm_id[41]; /**< local id (for session control) */ + char c2s_id[10]; /**< remote id (for session control) */ + + pkt_t pres; /**< copy of the last presence packet we received */ + + int available; /**< true if this session is available */ + int invisible; /**< true if this session is invisible */ + int pri; /**< current priority of this session */ + + jid_t A; /**< list of jids that this session has sent directed presence to */ + jid_t E; /**< list of jids that bounced presence updates we sent them */ + + void **module_data; /**< per-session module data */ + + sess_t next; /**< next session (in a list of sessions) */ +}; + +extern sig_atomic_t sm_lost_router; + +/* functions */ +xht aci_load(sm_t sm); +int aci_check(xht acls, char *type, jid_t jid); +void aci_unload(xht acls); + +int sm_sx_callback(sx_t s, sx_event_t e, void *data, void *arg); +int sm_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg); +void sm_timestamp(time_t t, char timestamp[18]); +void sm_c2s_action(sess_t dest, char *action, char *target); +void sm_signature(sm_t sm, char *str); + +int sm_register_ns(sm_t sm, char *uri); +void sm_unregister_ns(sm_t sm, char *uri); +int sm_get_ns(sm_t sm, char *uri); + +void dispatch(sm_t sm, pkt_t pkt); + +pkt_t pkt_error(pkt_t pkt, int err); +pkt_t pkt_tofrom(pkt_t pkt); +pkt_t pkt_dup(pkt_t pkt, const char *to, const char *from); +pkt_t pkt_new(sm_t sm, nad_t nad); +void pkt_free(pkt_t pkt); +pkt_t pkt_create(sm_t sm, const char *elem, const char *type, const char *to, const char *from); +void pkt_id(pkt_t src, pkt_t dest); +void pkt_id_new(pkt_t pkt); +void pkt_delay(pkt_t pkt, time_t t, const char *from); + +void pkt_router(pkt_t pkt); +void pkt_sess(pkt_t pkt, sess_t sess); + +int pres_trust(user_t user, jid_t jid); +void pres_roster(sess_t sess, item_t item); +void pres_update(sess_t sess, pkt_t pres); +void pres_error(sess_t sess, jid_t jid); +void pres_deliver(sess_t sess, pkt_t pres); +void pres_in(user_t user, pkt_t pres); +void pres_probe(user_t user); + +void sess_route(sess_t sess, pkt_t pkt); +sess_t sess_start(sm_t sm, jid_t jid); +void sess_end(sess_t sess); +sess_t sess_match(user_t user, char *resource); + +user_t user_load(sm_t sm, jid_t jid); +void user_free(user_t user); +int user_create(sm_t sm, jid_t jid); +void user_delete(sm_t sm, jid_t jid); + +void feature_register(sm_t sm, char *feature); +void feature_unregister(sm_t sm, char *feature); + + +/* driver module manager */ + +/** module return values */ +typedef enum { + mod_HANDLED, /**< packet was handled (and freed) */ + mod_PASS /**< packet was unhandled, should be passed to the next module */ +} mod_ret_t; + +/** module chain types */ +typedef enum { + chain_SESS_START, /**< session start, load per-session data */ + chain_SESS_END, /**< session ended, save & free per-session data */ + chain_IN_SESS, /**< packet from an active session */ + chain_IN_ROUTER, /**< packet from the router */ + chain_OUT_SESS, /**< packet to an active session */ + chain_OUT_ROUTER, /**< packet to a router */ + chain_PKT_SM, /**< packet for the sm itself */ + chain_PKT_USER, /**< packet for a user */ + chain_PKT_ROUTER, /**< packet from the router (special purpose) */ + chain_USER_LOAD, /**< user loaded, load per-user data */ + chain_USER_CREATE, /**< user creation, generate and save per-user data */ + chain_USER_DELETE /**< user deletion, delete saved per-user data */ +} mod_chain_t; + +typedef struct module_st *module_t; +typedef struct mod_instance_st *mod_instance_t; + +/** module manager date */ +struct mm_st { + sm_t sm; /**< sm context */ + + xht modules; /**< pointers to module data (key is module name) */ + + int nindex; /**< counter for module instance sequence (!!! should be local to mm_new) */ + + /** sess-start chain */ + mod_instance_t *sess_start; int nsess_start; + /** sess-end chain */ + mod_instance_t *sess_end; int nsess_end; + /** in-sess chain */ + mod_instance_t *in_sess; int nin_sess; + /** in-router chain */ + mod_instance_t *in_router; int nin_router; + /** out-sess chain */ + mod_instance_t *out_sess; int nout_sess; + /** out-router chain */ + mod_instance_t *out_router; int nout_router; + /** pkt-sm chain */ + mod_instance_t *pkt_sm; int npkt_sm; + /** pkt-user chain */ + mod_instance_t *pkt_user; int npkt_user; + /** pkt-router chain */ + mod_instance_t *pkt_router; int npkt_router; + /** user-load chain */ + mod_instance_t *user_load; int nuser_load; + /** user-create chain */ + mod_instance_t *user_create; int nuser_create; + /** user-delete chain */ + mod_instance_t *user_delete; int nuser_delete; +}; + +/** data for a single module */ +struct module_st { + mm_t mm; /**< module manager */ + + char *name; /**< name of module */ + + int index; /**< module index. this is the index into user->module_data and + sess->module_data where the module can store its own + per-user/per-session data */ + + void *handle; /**< module handle */ + + int (*module_init_fn)(mod_instance_t); /**< module init function */ + + int init; /**< number of times the module intialiser has been called */ + + void *private; /**< module private data */ + + int (*sess_start)(mod_instance_t mi, sess_t sess); /**< sess-start handler */ + void (*sess_end)(mod_instance_t mi, sess_t sess); /**< sess-end handler */ + + mod_ret_t (*in_sess)(mod_instance_t mi, sess_t sess, pkt_t pkt); /**< in-sess handler */ + mod_ret_t (*in_router)(mod_instance_t mi, pkt_t pkt); /**< in-router handler */ + + mod_ret_t (*out_sess)(mod_instance_t mi, sess_t sess, pkt_t pkt); /**< out-sess handler */ + mod_ret_t (*out_router)(mod_instance_t mi, pkt_t pkt); /**< out-router handler */ + + mod_ret_t (*pkt_sm)(mod_instance_t mi, pkt_t pkt); /**< pkt-sm handler */ + mod_ret_t (*pkt_user)(mod_instance_t mi, user_t user, pkt_t pkt); /**< pkt-user handler */ + + mod_ret_t (*pkt_router)(mod_instance_t mi, pkt_t pkt); /**< pkt-router handler */ + + int (*user_load)(mod_instance_t mi, user_t user); /**< user-load handler */ + + int (*user_create)(mod_instance_t mi, jid_t jid); /**< user-create handler */ + void (*user_delete)(mod_instance_t mi, jid_t jid); /**< user-delete handler */ + + void (*free)(module_t mod); /**< called when module is freed */ +}; + +/** single instance of a module in a chain */ +struct mod_instance_st { + sm_t sm; /**< sm context */ + + module_t mod; /**< module that this is an instance of */ + + int seq; /**< number of this instance */ + + mod_chain_t chain; /**< chain this instance is in */ + + char *arg; /**< option arg that this instance was started with */ +}; + +/** allocate a module manager instance, and loads the modules */ +mm_t mm_new(sm_t sm); +/** free a mm instance */ +void mm_free(mm_t mm); + +/** fire sess-start chain */ +int mm_sess_start(mm_t mm, sess_t sess); +/** fire sess-end chain */ +void mm_sess_end(mm_t mm, sess_t sess); + +/** fire in-sess chain */ +mod_ret_t mm_in_sess(mm_t mm, sess_t sess, pkt_t pkt); +/** fire in-router chain */ +mod_ret_t mm_in_router(mm_t mm, pkt_t pkt); + +/** fire out-sess chain */ +mod_ret_t mm_out_sess(mm_t mm, sess_t sess, pkt_t pkt); +/** fire out-router chain */ +mod_ret_t mm_out_router(mm_t mm, pkt_t pkt); + +/** fire pkt-sm chain */ +mod_ret_t mm_pkt_sm(mm_t mm, pkt_t pkt); +/** fire pkt-user chain */ +mod_ret_t mm_pkt_user(mm_t mm, user_t user, pkt_t pkt); + +/** fire pkt-router chain */ +mod_ret_t mm_pkt_router(mm_t mm, pkt_t pkt); + +/** fire user-load chain */ +int mm_user_load(mm_t mm, user_t user); + +/** fire user-create chain */ +int mm_user_create(mm_t mm, jid_t jid); +/** fire user-delete chain */ +void mm_user_delete(mm_t mm, jid_t jid); + + +/* object sets */ + +/** object types */ +typedef enum { + os_type_BOOLEAN, /**< boolean (0 or 1) */ + os_type_INTEGER, /**< integer */ + os_type_STRING, /**< string */ + os_type_NAD, /**< XML */ + os_type_UNKNOWN /**< unknown */ +} os_type_t; + +/** a single tuple (value) within an object */ +typedef struct os_field_st { + char *key; /**< field name */ + void *val; /**< field value */ + os_type_t type; /**< field type */ +} *os_field_t; + +typedef struct os_st *os_t; +typedef struct os_object_st *os_object_t; + +/** object set (ie group of several objects) */ +struct os_st { + pool p; /**< pool the objects are allocated from */ + + os_object_t head; /**< first object in the list */ + os_object_t tail; /**< last object in the list */ + + int count; /**< number of objects in this set */ + + os_object_t iter; /**< pointer for iteration */ +}; + +/** an object */ +struct os_object_st { + /** object set this object is part of */ + os_t os; + + /** fields (key is field name) */ + xht hash; + + os_object_t next; /**< next object in the list */ + os_object_t prev; /**< previous object in the list */ +}; + +/** create a new object set */ +os_t os_new(void); +/** free an object set */ +void os_free(os_t os); + +/** number of objects in a set */ +int os_count(os_t os); + +/** set iterator to first object (1 = exists, 0 = doesn't exist) */ +int os_iter_first(os_t os); + +/** set iterator to next object (1 = exists, 0 = doesn't exist) */ +int os_iter_next(os_t os); + +/** get the object currently under the iterator */ +os_object_t os_iter_object(os_t os); + +/** create a new object in this set */ +os_object_t os_object_new(os_t os); +/** free an object (remove it from its set) */ +void os_object_free(os_object_t o); + +/** add a field to the object */ +void os_object_put(os_object_t o, const char *key, const void *val, os_type_t type); + +/** get a field from the object of type type (result in val), ret 0 == not found */ +int os_object_get(os_t os, os_object_t o, const char *key, void **val, os_type_t type, os_type_t *ot); + +/** wrappers for os_object_get to avoid breaking strict-aliasing rules in gcc3 */ +int os_object_get_nad(os_t os, os_object_t o, const char *key, nad_t *val); +int os_object_get_str(os_t os, os_object_t o, const char *key, char **val); +int os_object_get_int(os_t os, os_object_t o, const char *key, int *val); +int os_object_get_bool(os_t os, os_object_t o, const char *key, int *val); +int os_object_get_time(os_t os, os_object_t o, const char *key, time_t *val); + +/** wrappers for os_object_put to avoid breaking strict-aliasing rules in gcc3 */ +void os_object_put_time(os_object_t o, const char *key, const time_t *val); + +/** set field iterator to first field (1 = exists, 0 = doesn't exist) */ +int os_object_iter_first(os_object_t o); +/** set field iterator to next field (1 = exists, 0 = doesn't exist) */ +int os_object_iter_next(os_object_t o); +/** extract field values from field currently under the iterator */ +void os_object_iter_get(os_object_t o, char **key, void **val, os_type_t *type); + + +/* storage manager */ + +/** storage driver return values */ +typedef enum { + st_SUCCESS, /**< call completed successful */ + st_FAILED, /**< call failed (driver internal error) */ + st_NOTFOUND, /**< no matching objects were found */ + st_NOTIMPL /**< call not implemented */ +} st_ret_t; + +typedef struct st_driver_st *st_driver_t; + +/** storage manager data */ +struct storage_st { + sm_t sm; /**< sm context */ + + xht drivers; /**< pointers to drivers (key is driver name) */ + xht types; /**< pointers to drivers (key is type name) */ + + st_driver_t default_drv; /**< default driver (used when there is no module + explicitly registered for a type) */ +}; + +/** data for a single storage driver */ +struct st_driver_st { + storage_t st; /**< storage manager context */ + + char *name; /**< name of driver */ + + void *private; /**< driver private data */ + + /** called to find out if this driver can handle a particular type */ + st_ret_t (*add_type)(st_driver_t drv, const char *type); + + /** put handler */ + st_ret_t (*put)(st_driver_t drv, const char *type, const char *owner, os_t os); + /** get handler */ + st_ret_t (*get)(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t *os); + /** delete handler */ + st_ret_t (*delete)(st_driver_t drv, const char *type, const char *owner, const char *filter); + /** replace handler */ + st_ret_t (*replace)(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t os); + + /** called when driver is freed */ + void (*free)(st_driver_t drv); +}; + +/** allocate a storage manager instance */ +storage_t storage_new(sm_t sm); +/** free a storage manager instance */ +void storage_free(storage_t st); + +/** associate this data type with this driver */ +st_ret_t storage_add_type(storage_t st, const char *driver, const char *type); + +/** store objects in this set */ +st_ret_t storage_put(storage_t st, const char *type, const char *owner, os_t os); +/** get objects matching this filter */ +st_ret_t storage_get(storage_t st, const char *type, const char *owner, const char *filter, os_t *os); +/** delete objects matching this filter */ +st_ret_t storage_delete(storage_t st, const char *type, const char *owner, const char *filter); +/** replace objects matching this filter with objects in this set (atomic delete + get) */ +st_ret_t storage_replace(storage_t st, const char *type, const char *owner, const char *filter, os_t os); + +/** type for the driver init function */ +typedef st_ret_t (*st_driver_init_fn)(st_driver_t); + + +/** storage filter types */ +typedef enum { + st_filter_type_PAIR, /**< key=value pair */ + st_filter_type_AND, /**< and operator */ + st_filter_type_OR, /**< or operator */ + st_filter_type_NOT /**< not operator */ +} st_filter_type_t; + +typedef struct st_filter_st *st_filter_t; +/** filter abstraction */ +struct st_filter_st { + pool p; /**< pool that filter is allocated from */ + + st_filter_type_t type; /**< type of this filter */ + + char *key; /**< key for PAIR filters */ + char *val; /**< value for PAIR filters */ + + st_filter_t sub; /**< sub-filter for operator filters */ + + st_filter_t next; /**< next filter in a group */ +}; + +/** create a filter abstraction from a LDAP-like filter string */ +st_filter_t storage_filter(const char *filter); + +/** see if the object matches the filter */ +int storage_match(st_filter_t filter, os_object_t o, os_t os); diff --git a/sm/storage.c b/sm/storage.c new file mode 100644 index 00000000..5749ada0 --- /dev/null +++ b/sm/storage.c @@ -0,0 +1,510 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file sm/storage.c + * @brief storage manager + * @author Robert Norris + * $Date: 2005/06/02 06:31:10 $ + * $Revision: 1.21 $ + */ + +/* these functions implement a multiplexor to get calls to the correct driver + * for the given type */ + +#include "sm.h" + +#include + +/* if you add a driver, you'll need to update these arrays */ +#ifdef STORAGE_DB +extern st_ret_t st_db_init(st_driver_t); +#endif +#ifdef STORAGE_FS +extern st_ret_t st_fs_init(st_driver_t); +#endif +#ifdef STORAGE_MYSQL +extern st_ret_t st_mysql_init(st_driver_t); +#endif +#ifdef STORAGE_PGSQL +extern st_ret_t st_pgsql_init(st_driver_t); +#endif +#ifdef STORAGE_ORACLE +extern st_ret_t st_oracle_init(st_driver_t); +#endif +#ifdef STORAGE_SQLITE +extern st_ret_t st_sqlite_init(st_driver_t); +#endif + +static char *st_driver_names[] = { +#ifdef STORAGE_DB + "db", +#endif +#ifdef STORAGE_FS + "fs", +#endif +#ifdef STORAGE_MYSQL + "mysql", +#endif +#ifdef STORAGE_PGSQL + "pgsql", +#endif +#ifdef STORAGE_ORACLE + "oracle", +#endif +#ifdef STORAGE_SQLITE + "sqlite", +#endif + NULL +}; + +static st_driver_init_fn st_driver_inits[] = { +#ifdef STORAGE_DB + st_db_init, +#endif +#ifdef STORAGE_FS + st_fs_init, +#endif +#ifdef STORAGE_MYSQL + st_mysql_init, +#endif +#ifdef STORAGE_PGSQL + st_pgsql_init, +#endif +#ifdef STORAGE_ORACLE + st_oracle_init, +#endif +#ifdef STORAGE_SQLITE + st_sqlite_init, +#endif + NULL +}; + + +storage_t storage_new(sm_t sm) { + storage_t st; + int i, j; + config_elem_t elem; + char *type; + st_ret_t ret; + + st = (storage_t) malloc(sizeof(struct storage_st)); + memset(st, 0, sizeof(struct storage_st)); + + st->sm = sm; + st->drivers = xhash_new(101); + st->types = xhash_new(101); + + /* register types declared in the config file */ + elem = config_get(sm->config, "storage.driver"); + if(elem != NULL) { + for(i = 0; i < elem->nvalues; i++) { + type = j_attr((const char **) elem->attrs[i], "type"); + for(j = 0; st_driver_names[j] != NULL; j++) { + if(strcmp(elem->values[i], st_driver_names[j]) == 0) { + if(type == NULL) + ret = storage_add_type(st, st_driver_names[j], NULL); + else + ret = storage_add_type(st, st_driver_names[j], type); + /* Initialisation of storage type failed */ + if (ret != st_SUCCESS) { + free(st); + return NULL; + } + } + } + } + } + + return st; +} + +static void _st_driver_reaper(xht drivers, const char *driver, void *val, void *arg) { + st_driver_t drv = (st_driver_t) val; + + (drv->free)(drv); + + free(drv); +} + +void storage_free(storage_t st) { + /* close down drivers */ + xhash_walk(st->drivers, _st_driver_reaper, NULL); + + xhash_free(st->drivers); + xhash_free(st->types); + free(st); +} + +st_ret_t storage_add_type(storage_t st, const char *driver, const char *type) { + st_driver_t drv; + st_driver_init_fn init = NULL; + int i; + st_ret_t ret; + + /* startup, see if we've already registered this type */ + if(type == NULL) { + log_debug(ZONE, "adding arbitrary types to driver '%s'", driver); + + /* see if we already have one */ + if(st->default_drv != NULL) { + log_debug(ZONE, "we already have a default handler, ignoring this one"); + return st_FAILED; + } + } else { + log_debug(ZONE, "adding type '%s' to driver '%s'", type, driver); + + /* see if we already have one */ + if(xhash_get(st->types, type) != NULL) { + log_debug(ZONE, "we already have a handler for type '%s', ignoring this one"); + return st_FAILED; + } + } + + /* get the driver */ + drv = xhash_get(st->drivers, driver); + if(drv == NULL) { + log_debug(ZONE, "driver not loaded, trying to init"); + + /* find the init function */ + for(i = 0; st_driver_names[i] != NULL; i++) { + if(strcmp(driver, st_driver_names[i]) == 0) { + init = st_driver_inits[i]; + break; + } + } + + /* d'oh */ + if(init == NULL) { + log_debug(ZONE, "no init function for driver '%s'", driver); + + return st_FAILED; + } + + /* make a new driver structure */ + drv = (st_driver_t) malloc(sizeof(struct st_driver_st)); + memset(drv, 0, sizeof(struct st_driver_st)); + + drv->st = st; + + log_debug(ZONE, "calling driver initializer"); + + /* init */ + if((init)(drv) == st_FAILED) { + log_write(st->sm->log, LOG_NOTICE, "initialisation of storage driver '%s' failed", driver); + free(drv); + return st_FAILED; + } + + /* add it to the drivers hash so we can find it later */ + drv->name = pstrdup(xhash_pool(st->drivers), driver); + xhash_put(st->drivers, drv->name, (void *) drv); + + log_write(st->sm->log, LOG_NOTICE, "initialised storage driver '%s'", driver); + } + + /* if its a default, set it up as such */ + if(type == NULL) { + st->default_drv = drv; + return st_SUCCESS; + } + + /* its a real type, so let the driver know */ + if(type != NULL && (ret = (drv->add_type)(drv, type)) != st_SUCCESS) { + log_debug(ZONE, "driver '%s' can't handle '%s' data", driver, type); + return ret; + } + + /* register the type */ + xhash_put(st->types, pstrdup(xhash_pool(st->types), type), (void *) drv); + + return st_SUCCESS; +} + +st_ret_t storage_put(storage_t st, const char *type, const char *owner, os_t os) { + st_driver_t drv; + st_ret_t ret; + + log_debug(ZONE, "storage_put: type=%s owner=%s os=%X", type, owner, os); + + /* find the handler for this type */ + drv = xhash_get(st->types, type); + if(drv == NULL) { + /* never seen it before, so it goes to the default driver */ + drv = st->default_drv; + if(drv == NULL) { + log_debug(ZONE, "no driver associated with type, and no default driver"); + + return st_NOTIMPL; + } + + /* register the type */ + ret = storage_add_type(st, drv->name, type); + if(ret != st_SUCCESS) + return ret; + } + + return (drv->put)(drv, type, owner, os); +} + +st_ret_t storage_get(storage_t st, const char *type, const char *owner, const char *filter, os_t *os) { + st_driver_t drv; + st_ret_t ret; + + log_debug(ZONE, "storage_get: type=%s owner=%s filter=%s", type, owner, filter); + + /* find the handler for this type */ + drv = xhash_get(st->types, type); + if(drv == NULL) { + /* never seen it before, so it goes to the default driver */ + drv = st->default_drv; + if(drv == NULL) { + log_debug(ZONE, "no driver associated with type, and no default driver"); + + return st_NOTIMPL; + } + + /* register the type */ + ret = storage_add_type(st, drv->name, type); + if(ret != st_SUCCESS) + return ret; + } + + return (drv->get)(drv, type, owner, filter, os); +} + +st_ret_t storage_delete(storage_t st, const char *type, const char *owner, const char *filter) { + st_driver_t drv; + st_ret_t ret; + + log_debug(ZONE, "storage_zap: type=%s owner=%s filter=%s", type, owner, filter); + + /* find the handler for this type */ + drv = xhash_get(st->types, type); + if(drv == NULL) { + /* never seen it before, so it goes to the default driver */ + drv = st->default_drv; + if(drv == NULL) { + log_debug(ZONE, "no driver associated with type, and no default driver"); + + return st_NOTIMPL; + } + + /* register the type */ + ret = storage_add_type(st, drv->name, type); + if(ret != st_SUCCESS) + return ret; + } + + return (drv->delete)(drv, type, owner, filter); +} + +st_ret_t storage_replace(storage_t st, const char *type, const char *owner, const char *filter, os_t os) { + st_driver_t drv; + st_ret_t ret; + + log_debug(ZONE, "storage_replace: type=%s owner=%s filter=%s os=%X", type, owner, filter, os); + + /* find the handler for this type */ + drv = xhash_get(st->types, type); + if(drv == NULL) { + /* never seen it before, so it goes to the default driver */ + drv = st->default_drv; + if(drv == NULL) { + log_debug(ZONE, "no driver associated with type, and no default driver"); + + return st_NOTIMPL; + } + + /* register the type */ + ret = storage_add_type(st, drv->name, type); + if(ret != st_SUCCESS) + return ret; + } + + return (drv->replace)(drv, type, owner, filter, os); +} + +static st_filter_t _storage_filter(pool p, const char *f, int len) { + char *c, *key, *val, *sub; + int vallen; + st_filter_t res, sf; + + if(f[0] != '(' && f[len] != ')') + return NULL; + + /* key/value pair */ + + /* if value is numeric, then represented as is. */ + /* if value is string, it is preceded by length: e.g. "key=5:abcde" */ + /* (needed to pass values which include a closing bracket ')', e.g. in resourcenames */ + + if(isalpha(f[1])) { + key = strdup(f+1); + + c = strchr(key, '='); + if(c == NULL) { + free(key); + return NULL; + } + *c = '\0'; c++; + + val = c; + + /* decide whether number or string by checking for ':' before ')' */ + + while (*c != ':' && *c != ')' && *c) + c++; + + if (!*c) { + free(key); + return NULL; + } + + if (*c == ':') { + /* string */ + *c = '\0'; + vallen = atoi(val); + c++; + val = c; + c += vallen; + } + + *c = '\0'; + log_debug(ZONE, "extracted key %s val %s", key, val); + + res = pmalloco(p, sizeof(struct st_filter_st)); + res->p = p; + + res->type = st_filter_type_PAIR; + res->key = pstrdup(p, key); + res->val = pstrdup(p, val); + + free(key); + return res; + } + + /* operator */ + if(f[1] != '&' && f[1] != '|' && f[2] != '!') + return NULL; + + res = pmalloco(p, sizeof(struct st_filter_st)); + res->p = p; + + switch(f[1]) { + case '&': res->type = st_filter_type_AND; break; + case '|': res->type = st_filter_type_OR; break; + case '!': res->type = st_filter_type_NOT; break; + } + + /* remove const for now, we will not change the string */ + c = (char *) &f[2]; + while(*c == '(') { + sub = c; + c = strchr(sub, ')'); + c++; + + sf = _storage_filter(p, (const char *) sub, c - sub); + + sf->next = res->sub; + res->sub = sf; + } + + return res; +} + +st_filter_t storage_filter(const char *filter) { + pool p; + st_filter_t f; + + if(filter == NULL) + return NULL; + + p = pool_new(); + + f = _storage_filter(p, filter, strlen(filter)); + if(f == NULL) + pool_free(p); + + return f; +} + +int _storage_match(st_filter_t f, os_object_t o, os_t os) { + void *val; + os_type_t ot; + st_filter_t scan; + + switch(f->type) { + case st_filter_type_PAIR: + if(!os_object_get(os, o, f->key, &val, os_type_UNKNOWN, &ot)) + return 0; + + switch(ot) { + case os_type_BOOLEAN: + if((atoi(f->val) != 0) == (((int) val) != 0)) + return 1; + return 0; + + case os_type_INTEGER: + if(atoi(f->val) == (int) val) + return 1; + return 0; + + case os_type_STRING: + if(strcmp(f->val, val) == 0) + return 1; + return 0; + + case os_type_NAD: + /* !!! this is hard, but probably not needed. if you need it, you implement it ;) */ + return 1; + + case os_type_UNKNOWN: + return 0; + } + + return 0; + + case st_filter_type_AND: + for(scan = f->sub; scan != NULL; scan = scan->next) + if(!_storage_match(scan, o, os)) + return 0; + return 1; + + case st_filter_type_OR: + for(scan = f->sub; scan != NULL; scan = scan->next) + if(_storage_match(scan, o, os)) + return 1; + return 0; + + case st_filter_type_NOT: + if(_storage_match(f->sub, o, os)) + return 0; + return 1; + } + + return 0; +} + +int storage_match(st_filter_t filter, os_object_t o, os_t os) { + if(filter == NULL) + return 1; + + return _storage_match(filter, o, os); +} diff --git a/sm/storage_db.c b/sm/storage_db.c new file mode 100644 index 00000000..c5e09a48 --- /dev/null +++ b/sm/storage_db.c @@ -0,0 +1,580 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file sm/storage_db.c + * @brief berkeley db storage module + * @author Robert Norris + * $Date: 2005/06/02 04:48:25 $ + * $Revision: 1.26 $ + */ + +/* + * !!! we must catch DB_RUNRECOVERY and call _st_db_panic(). I would argue that + * Berkeley should do this for all cases, not just for the process that + * caused the fault, but I'm not sure they see it that way. (I have asked, + * just waiting for a reply) + * + * Sleepycat SR#7019 resolved this. There is an unreleased patch available + * (I have a copy) that will be in 4.2 (due in June). + */ + +#include "sm.h" + +#ifdef STORAGE_DB + +#include + +/** internal structure, holds our data */ +typedef struct drvdata_st { + DB_ENV *env; + + char *path; + int sync; + + xht dbs; + + xht filters; +} *drvdata_t; + +/** internal structure, holds a single db handle */ +typedef struct dbdata_st { + drvdata_t data; + + DB *db; +} *dbdata_t; + +/* union for strict alias rules in gcc3 */ +union xhashv { + void **val; + dbdata_t *dbd_val; +}; + +static st_ret_t _st_db_add_type(st_driver_t drv, const char *type) { + drvdata_t data = (drvdata_t) drv->private; + dbdata_t dbd; + int err; + + dbd = (dbdata_t) malloc(sizeof(struct dbdata_st)); + memset(dbd, 0, sizeof(struct dbdata_st)); + + dbd->data = data; + + if((err = db_create(&(dbd->db), data->env, 0)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't create db handle: %s", db_strerror(err)); + free(dbd); + return st_FAILED; + } + + if((err = dbd->db->set_flags(dbd->db, DB_DUP)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't set database for duplicate storage: %s", db_strerror(err)); + dbd->db->close(dbd->db, 0); + free(dbd); + return st_FAILED; + } + + if((err = dbd->db->open(dbd->db, NULL, "sm.db", type, DB_HASH, DB_AUTO_COMMIT | DB_CREATE, 0)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't open storage db: %s", db_strerror(err)); + dbd->db->close(dbd->db, 0); + free(dbd); + return st_FAILED; + } + + xhash_put(data->dbs, type, dbd); + + return st_SUCCESS; +} + +/** make a new cursor (optionally wrapped in a txn) */ +static st_ret_t _st_db_cursor_new(st_driver_t drv, dbdata_t dbd, DBC **cursor, DB_TXN **txnid) { + int err; + + if(txnid != NULL) + if((err = dbd->data->env->txn_begin(dbd->data->env, NULL, txnid, DB_TXN_SYNC)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't begin new transaction: %s", db_strerror(err)); + return st_FAILED; + } + + if(txnid == NULL) + err = dbd->db->cursor(dbd->db, NULL, cursor, 0); + else + err = dbd->db->cursor(dbd->db, *txnid, cursor, 0); + + if(err != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't create cursor: %s", db_strerror(err)); + if(txnid != NULL) + (*txnid)->abort(*txnid); + return st_FAILED; + } + + return st_SUCCESS; +} + +/** close down a cursor */ +static st_ret_t _st_db_cursor_free(st_driver_t drv, dbdata_t dbd, DBC *cursor, DB_TXN *txnid) { + int err; + + if((err = cursor->c_close(cursor)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't close cursor: %s", db_strerror(err)); + if(txnid != NULL) + txnid->abort(txnid); + return st_FAILED; + } + + if(txnid != NULL) + if((err = txnid->commit(txnid, DB_TXN_SYNC)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't commit transaction: %s", db_strerror(err)); + return st_FAILED; + } + + return st_SUCCESS; +} + +static void _st_db_object_serialise(os_object_t o, char **buf, int *len) { + char *key, *xml, *xmlstr; + void *val; + os_type_t ot; + int cur = 0, xlen; + + log_debug(ZONE, "serialising object"); + + *buf = NULL; + *len = 0; + + if(os_object_iter_first(o)) + do { + os_object_iter_get(o, &key, &val, &ot); + + log_debug(ZONE, "serialising key %s", key); + + ser_string_set(key, &cur, buf, len); + ser_int_set(ot, &cur, buf, len); + + switch(ot) { + case os_type_BOOLEAN: + ser_int_set(((int) val) != 0, &cur, buf, len); + break; + + case os_type_INTEGER: + ser_int_set((int) val, &cur, buf, len); + break; + + case os_type_STRING: + ser_string_set((char *) val, &cur, buf, len); + break; + + case os_type_NAD: + nad_print((nad_t) val, 0, &xml, &xlen); + xmlstr = (char *) malloc(sizeof(char) * (xlen + 1)); + sprintf(xmlstr, "%.*s", xlen, xml); + ser_string_set(xmlstr, &cur, buf, len); + free(xmlstr); + break; + + case os_type_UNKNOWN: + break; + } + } while(os_object_iter_next(o)); + + *len = cur; +} + +static os_object_t _st_db_object_deserialise(st_driver_t drv, os_t os, const char *buf, int len) { + os_object_t o; + int cur; + char *key, *sval; + int ot; + int ival; + nad_t nad; + + log_debug(ZONE, "deserialising object"); + + o = os_object_new(os); + + cur = 0; + while(cur < len) { + if(ser_string_get(&key, &cur, buf, len) != 0 || ser_int_get(&ot, &cur, buf, len) != 0) { + log_debug(ZONE, "ran off the end of the buffer"); + return o; + } + + log_debug(ZONE, "deserialising key %s", key); + + switch((os_type_t) ot) { + case os_type_BOOLEAN: + ser_int_get(&ival, &cur, buf, len); + ival = (ival != 0); + os_object_put(o, key, &ival, os_type_BOOLEAN); + break; + + case os_type_INTEGER: + ser_int_get(&ival, &cur, buf, len); + os_object_put(o, key, &ival, os_type_INTEGER); + break; + + case os_type_STRING: + ser_string_get(&sval, &cur, buf, len); + os_object_put(o, key, sval, os_type_STRING); + free(sval); + break; + + case os_type_NAD: + ser_string_get(&sval, &cur, buf, len); + nad = nad_parse(drv->st->sm->router->nad_cache, sval, strlen(sval)); + free(sval); + if(nad == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "db: unable to parse stored XML - database corruption?"); + return NULL; + } + os_object_put(o, key, nad, os_type_NAD); + nad_free(nad); + break; + + case os_type_UNKNOWN: + break; + } + + free(key); + } + + return o; +} + +static st_ret_t _st_db_put_guts(st_driver_t drv, const char *type, const char *owner, os_t os, dbdata_t dbd, DBC *c, DB_TXN *t) { + DBT key, val; + os_object_t o; + char *buf; + int len, err; + + memset(&key, 0, sizeof(DBT)); + memset(&val, 0, sizeof(DBT)); + + key.data = (char *) owner; + key.size = strlen(owner); + + if(os_iter_first(os)) + do { + o = os_iter_object(os); + _st_db_object_serialise(o, &buf, &len); + + val.data = buf; + val.size = len; + + if((err = c->c_put(c, &key, &val, DB_KEYLAST)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't store value for type %s owner %s in storage db: %s", type, owner, db_strerror(err)); + free(buf); + return st_FAILED; + } + + free(buf); + + } while(os_iter_next(os)); + + return st_SUCCESS; +} + +static st_ret_t _st_db_put(st_driver_t drv, const char *type, const char *owner, os_t os) { + drvdata_t data = (drvdata_t) drv->private; + dbdata_t dbd = xhash_get(data->dbs, type); + DBC *c; + DB_TXN *t; + st_ret_t ret; + + if(os_count(os) == 0) + return st_SUCCESS; + + ret = _st_db_cursor_new(drv, dbd, &c, &t); + if(ret != st_SUCCESS) + return ret; + + ret = _st_db_put_guts(drv, type, owner, os, dbd, c, t); + if(ret != st_SUCCESS) { + t->abort(t); + _st_db_cursor_free(drv, dbd, c, NULL); + return st_FAILED; + } + + return _st_db_cursor_free(drv, dbd, c, t); +} + +static st_ret_t _st_db_get(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t *os) { + drvdata_t data = (drvdata_t) drv->private; + dbdata_t dbd = xhash_get(data->dbs, type); + DBC *c; + DB_TXN *t; + st_ret_t ret; + DBT key, val; + st_filter_t f; + int err; + os_object_t o; + char *cfilter; + + ret = _st_db_cursor_new(drv, dbd, &c, &t); + if(ret != st_SUCCESS) + return ret; + + f = NULL; + if(filter != NULL) { + f = xhash_get(data->filters, filter); + if(f == NULL) { + f = storage_filter(filter); + cfilter = pstrdup(xhash_pool(data->filters), filter); + xhash_put(data->filters, cfilter, (void *) f); + pool_cleanup(xhash_pool(data->filters), (pool_cleaner) pool_free, f->p); + } + } + + memset(&key, 0, sizeof(DBT)); + memset(&val, 0, sizeof(DBT)); + + key.data = (char *) owner; + key.size = strlen(owner); + + *os = os_new(); + + err = c->c_get(c, &key, &val, DB_SET); + while(err == 0) { + o = _st_db_object_deserialise(drv, *os, val.data, val.size); + + if(o != NULL && !storage_match(f, o, *os)) + os_object_free(o); + + err = c->c_get(c, &key, &val, DB_NEXT_DUP); + } + + if(err != 0 && err != DB_NOTFOUND) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't move cursor for type %s owner %s in storage db: %s", type, owner, db_strerror(err)); + t->abort(t); + _st_db_cursor_free(drv, dbd, c, NULL); + os_free(*os); + return st_FAILED; + } + + ret = _st_db_cursor_free(drv, dbd, c, t); + if(ret != st_SUCCESS) { + os_free(*os); + return ret; + } + + if(os_count(*os) == 0) { + os_free(*os); + return st_NOTFOUND; + } + + return st_SUCCESS; +} + +static st_ret_t _st_db_delete_guts(st_driver_t drv, const char *type, const char *owner, const char *filter, dbdata_t dbd, DBC *c, DB_TXN *t) { + drvdata_t data = (drvdata_t) drv->private; + DBT key, val; + st_filter_t f; + int err; + os_t os; + os_object_t o; + char *cfilter; + + f = NULL; + if(filter != NULL) { + f = xhash_get(data->filters, filter); + if(f == NULL) { + f = storage_filter(filter); + cfilter = pstrdup(xhash_pool(data->filters), filter); + xhash_put(data->filters, cfilter, (void *) f); + pool_cleanup(xhash_pool(data->filters), (pool_cleaner) pool_free, f->p); + } + } + + memset(&key, 0, sizeof(DBT)); + memset(&val, 0, sizeof(DBT)); + + key.data = (char *) owner; + key.size = strlen(owner); + + os = os_new(); + + err = c->c_get(c, &key, &val, DB_SET); + while(err == 0) { + o = _st_db_object_deserialise(drv, os, val.data, val.size); + + if(o != NULL && storage_match(f, o, os)) + err = c->c_del(c, 0); + + if(err == 0) + err = c->c_get(c, &key, &val, DB_NEXT_DUP); + } + + os_free(os); + + if(err != 0 && err != DB_NOTFOUND) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't move cursor for type %s owner %s in storage db: %s", type, owner, db_strerror(err)); + return st_FAILED; + } + + return st_SUCCESS; +} + +static st_ret_t _st_db_delete(st_driver_t drv, const char *type, const char *owner, const char *filter) { + drvdata_t data = (drvdata_t) drv->private; + dbdata_t dbd = xhash_get(data->dbs, type); + DBC *c; + DB_TXN *t; + st_ret_t ret; + + ret = _st_db_cursor_new(drv, dbd, &c, &t); + if(ret != st_SUCCESS) + return ret; + + ret = _st_db_delete_guts(drv, type, owner, filter, dbd, c, t); + if(ret != st_SUCCESS) { + t->abort(t); + _st_db_cursor_free(drv, dbd, c, NULL); + return st_FAILED; + } + + return _st_db_cursor_free(drv, dbd, c, t); +} + +static st_ret_t _st_db_replace(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t os) { + drvdata_t data = (drvdata_t) drv->private; + dbdata_t dbd = xhash_get(data->dbs, type); + DBC *c; + DB_TXN *t; + st_ret_t ret; + + ret = _st_db_cursor_new(drv, dbd, &c, &t); + if(ret != st_SUCCESS) + return ret; + + ret = _st_db_delete_guts(drv, type, owner, filter, dbd, c, t); + if(ret != st_SUCCESS) { + t->abort(t); + _st_db_cursor_free(drv, dbd, c, NULL); + return st_FAILED; + } + + if(os_count(os) == 0) + return _st_db_cursor_free(drv, dbd, c, t); + + ret = _st_db_put_guts(drv, type, owner, os, dbd, c, t); + if(ret != st_SUCCESS) { + t->abort(t); + _st_db_cursor_free(drv, dbd, c, NULL); + return st_FAILED; + } + + return _st_db_cursor_free(drv, dbd, c, t); +} + +static void _st_db_free(st_driver_t drv) { + drvdata_t data = (drvdata_t) drv->private; + const char *key; + dbdata_t dbd; + DB_ENV *env; + union xhashv xhv; + + xhv.dbd_val = &dbd; + if(xhash_iter_first(data->dbs)) + do { + xhash_iter_get(data->dbs, &key, xhv.val); + + log_debug(ZONE, "closing %s db", key); + + dbd->db->close(dbd->db, 0); + free(dbd); + } while(xhash_iter_next(data->dbs)); + + xhash_free(data->dbs); + + xhash_free(data->filters); + + data->env->close(data->env, 0); + + /* remove db environment files if no longer in use */ + if (db_env_create(&env, 0) == 0) + env->remove(env, data->path, 0); + + free(data); +} + +/** panic function */ +static void _st_db_panic(DB_ENV *env, int errval) { + log_t log = (log_t) env->app_private; + + log_write(log, LOG_CRIT, "db: corruption detected! close all jabberd processes and run db_recover"); + + exit(2); +} + +st_ret_t st_db_init(st_driver_t drv) { + char *path; + int err; + DB_ENV *env; + drvdata_t data; + + path = config_get_one(drv->st->sm->config, "storage.db.path", 0); + if(path == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "db: no path specified in config file"); + return st_FAILED; + } + + if((err = db_env_create(&env, 0)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't create environment: %s", db_strerror(err)); + return st_FAILED; + } + + if((err = env->set_paniccall(env, _st_db_panic)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't set panic call: %s", db_strerror(err)); + return st_FAILED; + } + + /* store the log context in case we panic */ + env->app_private = drv->st->sm->log; + + if((err = env->open(env, path, DB_INIT_LOCK | DB_INIT_MPOOL | DB_INIT_LOG | DB_INIT_TXN | DB_CREATE, 0)) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "db: couldn't open environment: %s", db_strerror(err)); + env->close(env, 0); + return st_FAILED; + } + + data = (drvdata_t) malloc(sizeof(struct drvdata_st)); + memset(data, 0, sizeof(struct drvdata_st)); + + data->env = env; + data->path = path; + + if(config_get_one(drv->st->sm->config, "storage.db.sync", 0) != NULL) + data->sync = 1; + + data->dbs = xhash_new(101); + + data->filters = xhash_new(17); + + drv->private = (void *) data; + + drv->add_type = _st_db_add_type; + drv->put = _st_db_put; + drv->get = _st_db_get; + drv->replace = _st_db_replace; + drv->delete = _st_db_delete; + drv->free = _st_db_free; + + return st_SUCCESS; +} + +#endif diff --git a/sm/storage_fs.c b/sm/storage_fs.c new file mode 100644 index 00000000..18e6f07f --- /dev/null +++ b/sm/storage_fs.c @@ -0,0 +1,506 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file sm/storage_fs.c + * @brief filesystem storage module + * @author Robert Norris + * $Date: 2005/06/02 04:48:25 $ + * $Revision: 1.15 $ + */ + +/* + * WARNING: this uses lots of static buffers, and doesn't do all the bounds + * checking that it should. it should not be used for anything other than + * testing + * + * !!! fix everything that makes this a problem + */ + +#include "sm.h" + +#ifdef STORAGE_FS + +#include + +#ifdef HAVE_DIRENT_H +# include +# define NAMELEN(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NAMELEN(dirent) (dirent)->d_namelen +# ifdef HAVE_SYS_NDIR_H +# include +# endif +# ifdef HAVE_SYS_DIR_H +# include +# endif +# ifdef HAVE_NDIR_H +# include +# endif +#endif + +/** internal structure, holds our data */ +typedef struct drvdata_st { + char *path; +} *drvdata_t; + +static st_ret_t _st_fs_add_type(st_driver_t drv, const char *type) { + drvdata_t data = (drvdata_t) drv->private; + char path[1024]; + struct stat sbuf; + int ret; + + snprintf(path, 1024, "%s/%s", data->path, type); + ret = stat(path, &sbuf); + if(ret < 0) { + if(errno != ENOENT) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't stat '%s': %s", path, strerror(errno)); + return st_FAILED; + } + + log_debug(ZONE, "creating new type dir '%s'", path); + + ret = mkdir(path, 0755); + if(ret < 0) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't create directory '%s': %s", path, strerror(errno)); + return st_FAILED; + } + } + + return st_SUCCESS; +} + +static st_ret_t _st_fs_put(st_driver_t drv, const char *type, const char *owner, os_t os) { + drvdata_t data = (drvdata_t) drv->private; + char path[1024]; + struct stat sbuf; + int ret; + int file; + FILE *f; + os_object_t o; + char *key; + void *val; + os_type_t ot; + char *xml; + int len; + + if(os_count(os) == 0) + return st_SUCCESS; + + snprintf(path, 1024, "%s/%s", data->path, type); + ret = stat(path, &sbuf); + if(ret < 0) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't stat '%s': %s", path, strerror(errno)); + return st_FAILED; + } + + snprintf(path, 1024, "%s/%s/%s", data->path, type, owner); + ret = stat(path, &sbuf); + if(ret < 0) { + if(errno != ENOENT) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't stat '%s': %s", path, strerror(errno)); + return st_FAILED; + } + + log_debug(ZONE, "creating new collection dir '%s'", path); + + ret = mkdir(path, 0755); + if(ret < 0) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't create directory '%s': %s", path, strerror(errno)); + return st_FAILED; + } + } + + file = -1; + + if(os_iter_first(os)) + do { + for(file++; file < 999999; file++) { + snprintf(path, 1024, "%s/%s/%s/%d", data->path, type, owner, file); + + ret = stat(path, &sbuf); + if(ret < 0 && errno == ENOENT) + break; + + if(ret < 0) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't stat '%s': %s", path, strerror(errno)); + return st_FAILED; + } + } + + log_debug(ZONE, "will store object to %s", path); + + f = fopen(path, "w"); + if(f == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't open '%s' for writing: %s", path, strerror(errno)); + return st_FAILED; + } + + o = os_iter_object(os); + + if(os_object_iter_first(o)) + do { + os_object_iter_get(o, &key, &val, &ot); + + log_debug(ZONE, "writing field %s type %d", key, ot); + + switch(ot) { + case os_type_BOOLEAN: + fprintf(f, "%s %d %d\n", key, ot, (int) val == 0 ? 0 : 1); + break; + + case os_type_INTEGER: + fprintf(f, "%s %d %d\n", key, ot, (int) val); + break; + + case os_type_STRING: + fprintf(f, "%s %d %s\n", key, ot, (char *) val); + break; + + case os_type_NAD: + nad_print((nad_t) val, 0, &xml, &len); + fprintf(f, "%s %d %.*s\n", key, ot, len, xml); + break; + + case os_type_UNKNOWN: + break; + } + + } while(os_object_iter_next(o)); + + fclose(f); + + } while(os_iter_next(os)); + + return st_SUCCESS; +} + +static st_ret_t _st_fs_get(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t *os) { + drvdata_t data = (drvdata_t) drv->private; + char path[1024], file[1024]; + struct stat sbuf; + int ret; + DIR *dir; + struct dirent *dirent; + FILE *f; + char buf[4096], *otc, *val, *c; + os_object_t o; + os_type_t ot; + int i; + nad_t nad; + st_filter_t sf; + + snprintf(path, 1024, "%s/%s/%s", data->path, type, owner); + ret = stat(path, &sbuf); + if(ret < 0) { + if(errno == ENOENT) + return st_NOTFOUND; + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't stat '%s': %s", path, strerror(errno)); + return st_FAILED; + } + + dir = opendir(path); + if(dir == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't open directory '%s': %s", path, strerror(errno)); + return st_FAILED; + } + + *os = os_new(); + + errno = 0; + while((dirent = readdir(dir)) != NULL) { + if(!(isdigit(dirent->d_name[0]))) + continue; + + snprintf(file, 1024, "%s/%s", path, dirent->d_name); + f = fopen(file, "r"); + if(f == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't open '%s' for reading: %s", path, strerror(errno)); + os_free(*os); + closedir(dir); + return st_FAILED; + } + + o = os_object_new(*os); + + while(fgets(buf, 4096, f) != NULL) { + otc = strchr(buf, ' '); + *otc = '\0'; otc++; + + val = strchr(otc, ' '); + *val = '\0'; val++; + + ot = (os_type_t) atoi(otc); + + switch(ot) { + case os_type_BOOLEAN: + case os_type_INTEGER: + i = atoi(val); + os_object_put(o, buf, &i, ot); + + break; + + case os_type_STRING: + c = strchr(val, '\n'); + if(c != NULL) *c = '\0'; + os_object_put(o, buf, val, ot); + + break; + + case os_type_NAD: + nad = nad_parse(drv->st->sm->router->nad_cache, val, 0); + if(nad == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "fs: unable to parse stored XML; type=%s, owner=%s", type, owner); + os_free(*os); + fclose(f); + closedir(dir); + return st_FAILED; + } + os_object_put(o, buf, nad, ot); + nad_free(nad); + + break; + + case os_type_UNKNOWN: + break; + } + } + + if(!feof(f)) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't read from '%s': %s", path, strerror(errno)); + os_free(*os); + fclose(f); + closedir(dir); + return st_FAILED; + } + + fclose(f); + + errno = 0; + } + + if(errno != 0) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't read from directory '%s': %s", path, strerror(errno)); + closedir(dir); + os_free(*os); + return st_FAILED; + } + + closedir(dir); + + sf = storage_filter(filter); + + if(os_iter_first(*os)) + do { + o = os_iter_object(*os); + if(!storage_match(sf, o, *os)) + os_object_free(o); + } while(os_iter_next(*os)); + + if(sf != NULL) pool_free(sf->p); + + return st_SUCCESS; +} + +static st_ret_t _st_fs_delete(st_driver_t drv, const char *type, const char *owner, const char *filter) { + drvdata_t data = (drvdata_t) drv->private; + char path[1024], file[1024]; + struct stat sbuf; + int ret; + DIR *dir; + os_t os; + struct dirent *dirent; + FILE *f; + char buf[4096], *otc, *val, *c; + os_object_t o; + os_type_t ot; + int i; + nad_t nad; + st_filter_t sf; + + snprintf(path, 1024, "%s/%s/%s", data->path, type, owner); + ret = stat(path, &sbuf); + if(ret < 0) { + if(errno == ENOENT) + return st_NOTFOUND; + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't stat '%s': %s", path, strerror(errno)); + return st_FAILED; + } + + dir = opendir(path); + if(dir == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't open directory '%s': %s", path, strerror(errno)); + return st_FAILED; + } + + os = os_new(); + + sf = storage_filter(filter); + + errno = 0; + while((dirent = readdir(dir)) != NULL) { + if(!(isdigit(dirent->d_name[0]))) + continue; + + snprintf(file, 1024, "%s/%s", path, dirent->d_name); + f = fopen(file, "r"); + if(f == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't open '%s' for reading: %s", path, strerror(errno)); + os_free(os); + closedir(dir); + return st_FAILED; + } + + o = os_object_new(os); + + while(fgets(buf, 4096, f) != NULL) { + otc = strchr(buf, ' '); + *otc = '\0'; otc++; + + val = strchr(otc, ' '); + *val = '\0'; val++; + + ot = (os_type_t) atoi(otc); + + switch(ot) { + case os_type_BOOLEAN: + case os_type_INTEGER: + i = atoi(val); + os_object_put(o, buf, &i, ot); + + break; + + case os_type_STRING: + c = strchr(val, '\n'); + if(c != NULL) *c = '\0'; + os_object_put(o, buf, val, ot); + + break; + + case os_type_NAD: + nad = nad_parse(drv->st->sm->router->nad_cache, val, 0); + if(nad == NULL) + log_write(drv->st->sm->log, LOG_ERR, "fs: unable to parse stored XML; type=%s, owner=%s", type, owner); + else { + os_object_put(o, buf, nad, ot); + nad_free(nad); + } + + break; + + case os_type_UNKNOWN: + break; + } + } + + if(!feof(f)) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't read from '%s': %s", path, strerror(errno)); + os_free(os); + fclose(f); + closedir(dir); + return st_FAILED; + } + + fclose(f); + + if(storage_match(sf, o, os)) { + ret = unlink(file); + if(ret < 0) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't unlink '%s': %s", path, strerror(errno)); + if(sf != NULL) pool_free(sf->p); + os_free(os); + closedir(dir); + return st_FAILED; + } + } + + errno = 0; + } + + if(errno != 0) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't read from directory '%s': %s", path, strerror(errno)); + closedir(dir); + os_free(os); + return st_FAILED; + } + + if(sf != NULL) pool_free(sf->p); + + os_free(os); + + closedir(dir); + + return st_SUCCESS; +} + +static st_ret_t _st_fs_replace(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t os) { + st_ret_t ret; + + ret = _st_fs_delete(drv, type, owner, filter); + if(ret == st_SUCCESS || ret == st_NOTFOUND) + ret = _st_fs_put(drv, type, owner, os); + + return ret; +} + +static void _st_fs_free(st_driver_t drv) { + drvdata_t data = (drvdata_t) drv->private; + + free(data); +} + +st_ret_t st_fs_init(st_driver_t drv) { + char *path; + struct stat sbuf; + int ret; + drvdata_t data; + + path = config_get_one(drv->st->sm->config, "storage.fs.path", 0); + if(path == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "fs: no path specified in config file"); + return st_FAILED; + } + + ret = stat(path, &sbuf); + if(ret < 0) { + log_write(drv->st->sm->log, LOG_ERR, "fs: couldn't stat path '%s': %s", path, strerror(errno)); + return st_FAILED; + } + + data = (drvdata_t) malloc(sizeof(struct drvdata_st)); + memset(data, 0, sizeof(struct drvdata_st)); + + data->path = path; + + drv->private = (void *) data; + + drv->add_type = _st_fs_add_type; + drv->put = _st_fs_put; + drv->get = _st_fs_get; + drv->delete = _st_fs_delete; + drv->replace = _st_fs_replace; + drv->free = _st_fs_free; + + log_write(drv->st->sm->log, LOG_WARNING, "fs: the filesystem storage driver should only be used for testing!"); + + return st_SUCCESS; +} + +#endif diff --git a/sm/storage_mysql.c b/sm/storage_mysql.c new file mode 100644 index 00000000..ca1256e0 --- /dev/null +++ b/sm/storage_mysql.c @@ -0,0 +1,597 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file sm/storage_mysql.c + * @brief mysql storage module + * @author Robert Norris + * $Date: 2005/06/22 20:31:22 $ + * $Revision: 1.22 $ + */ + +#include "sm.h" + +#ifdef STORAGE_MYSQL + +#include + +/** internal structure, holds our data */ +typedef struct drvdata_st { + MYSQL *conn; + + char *prefix; + + xht filters; + + int txn; +} *drvdata_t; + +#define FALLBACK_BLOCKSIZE (4096) + +/** internal: do and return the math and ensure it gets realloc'd */ +static size_t _st_mysql_realloc(char **oblocks, size_t len) { + void *nblocks; + size_t nlen; + static size_t block_size = 0; + + if (block_size == 0) { +#ifdef HAVE_GETPAGESIZE + block_size = getpagesize(); +#elif defined(_SC_PAGESIZE) + block_size = sysconf(_SC_PAGESIZE); +#elif defined(_SC_PAGE_SIZE) + block_size = sysconf(_SC_PAGE_SIZE); +#else + block_size = FALLBACK_BLOCKSIZE; +#endif + } + /* round up to standard block sizes */ + nlen = (((len-1)/block_size)+1)*block_size; + + /* keep trying till we get it */ + while((nblocks = realloc(*oblocks, nlen)) == NULL) sleep(1); + *oblocks = nblocks; + return nlen; +} + +/** this is the safety check used to make sure there's always enough mem */ +#define MYSQL_SAFE(blocks, size, len) if((size) >= len) len = _st_mysql_realloc(&(blocks),(size + 1)); + +static void _st_mysql_convert_filter_recursive(st_driver_t drv, st_filter_t f, char **buf, int *buflen, int *nbuf) { + drvdata_t data = (drvdata_t) drv->private; + st_filter_t scan; + char *cval; + int vlen; + + switch(f->type) { + case st_filter_type_PAIR: + + /* do sql escape processing of f->val */ + cval = (char *) malloc(sizeof(char) * ((strlen((char *) f->val) * 2) + 1)); + vlen = mysql_real_escape_string(data->conn, cval, (char *) f->val, strlen((char *) f->val)); + + MYSQL_SAFE((*buf), *buflen + 12 + vlen - strlen(f->val), *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( `%s` = \'%s\' ) ", f->key, cval); + free(cval); + + break; + + case st_filter_type_AND: + MYSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( "); + + for(scan = f->sub; scan != NULL; scan = scan->next) { + _st_mysql_convert_filter_recursive(drv, scan, buf, buflen, nbuf); + + if(scan->next != NULL) { + MYSQL_SAFE((*buf), *buflen + 4, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "AND "); + } + } + + MYSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), ") "); + + return; + + case st_filter_type_OR: + MYSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( "); + + for(scan = f->sub; scan != NULL; scan = scan->next) { + _st_mysql_convert_filter_recursive(drv, scan, buf, buflen, nbuf); + + if(scan->next != NULL) { + MYSQL_SAFE((*buf), *buflen + 3, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "OR "); + } + } + + MYSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), ") "); + + return; + + case st_filter_type_NOT: + MYSQL_SAFE((*buf), *buflen + 6, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( NOT "); + + _st_mysql_convert_filter_recursive(drv, f->sub, buf, buflen, nbuf); + + MYSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), ") "); + + return; + } +} + +static char *_st_mysql_convert_filter(st_driver_t drv, const char *owner, const char *filter) { + drvdata_t data = (drvdata_t) drv->private; + char *buf = NULL, *sbuf = NULL, *cfilter; + int buflen = 0, nbuf = 0, fbuf; + st_filter_t f; + + MYSQL_SAFE(buf, 24 + strlen(owner), buflen); + + nbuf = sprintf(buf, "`collection-owner` = '%s'", owner); + + sbuf = xhash_get(data->filters, filter); + if(sbuf != NULL) { + MYSQL_SAFE(buf, buflen + strlen(sbuf) + 7, buflen); + nbuf += sprintf(&buf[nbuf], " AND %s", sbuf); + return buf; + } + + cfilter = pstrdup(xhash_pool(data->filters), filter); + + f = storage_filter(filter); + if(f == NULL) + return buf; + + MYSQL_SAFE(buf, buflen + 5, buflen); + nbuf += sprintf(&buf[nbuf], " AND "); + + fbuf = nbuf; + + _st_mysql_convert_filter_recursive(drv, f, &buf, &buflen, &nbuf); + + xhash_put(data->filters, cfilter, pstrdup(xhash_pool(data->filters), &buf[fbuf])); + + pool_free(f->p); + + return buf; +} + +static st_ret_t _st_mysql_add_type(st_driver_t drv, const char *type) { + return st_SUCCESS; +} + +static st_ret_t _st_mysql_put_guts(st_driver_t drv, const char *type, const char *owner, os_t os) { + drvdata_t data = (drvdata_t) drv->private; + char *left = NULL, *right = NULL; + int lleft = 0, lright = 0, nleft, nright; + os_object_t o; + char *key, *cval = NULL; + void *val; + int vlen; + os_type_t ot; + char *xml; + int xlen; + char tbuf[128]; + + if(os_count(os) == 0) + return st_SUCCESS; + + if(data->prefix != NULL) { + snprintf(tbuf, sizeof(tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + if(os_iter_first(os)) + do { + MYSQL_SAFE(left, strlen(type) + 36, lleft); + nleft = sprintf(left, "INSERT INTO `%s` ( `collection-owner`", type); + + MYSQL_SAFE(right, strlen(owner) + 15, lright); + nright = sprintf(right, " ) VALUES ( '%s'", owner); + + o = os_iter_object(os); + if(os_object_iter_first(o)) + do { + os_object_iter_get(o, &key, &val, &ot); + + switch(ot) { + case os_type_BOOLEAN: + cval = val ? strdup("1") : strdup("0"); + vlen = 1; + break; + + case os_type_INTEGER: + cval = (char *) malloc(sizeof(char) * 20); + sprintf(cval, "%d", (int) val); + vlen = strlen(cval); + break; + + case os_type_STRING: + cval = (char *) malloc(sizeof(char) * ((strlen((char *) val) * 2) + 1)); + vlen = mysql_real_escape_string(data->conn, cval, (char *) val, strlen((char *) val)); + break; + + case os_type_NAD: + nad_print((nad_t) val, 0, &xml, &xlen); + cval = (char *) malloc(sizeof(char) * ((xlen * 2) + 4)); + vlen = mysql_real_escape_string(data->conn, &cval[3], xml, xlen) + 3; + strncpy(cval, "NAD", 3); + break; + + case os_type_UNKNOWN: + break; + } + + log_debug(ZONE, "key %s val %s", key, cval); + + MYSQL_SAFE(left, lleft + strlen(key) + 4, lleft); + nleft += sprintf(&left[nleft], ", `%s`", key); + + MYSQL_SAFE(right, lright + strlen(cval) + 4, lright); + nright += sprintf(&right[nright], ", '%s'", cval); + + free(cval); + } while(os_object_iter_next(o)); + + MYSQL_SAFE(left, lleft + strlen(right) + 2, lleft); + sprintf(&left[nleft], "%s )", right); + + log_debug(ZONE, "prepared sql: %s", left); + + if(mysql_query(data->conn, left) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql insert failed: %s", mysql_error(data->conn)); + free(left); + free(right); + return st_FAILED; + } + + } while(os_iter_next(os)); + + free(left); + free(right); + + return st_SUCCESS; +} + +static st_ret_t _st_mysql_put(st_driver_t drv, const char *type, const char *owner, os_t os) { + drvdata_t data = (drvdata_t) drv->private; + + if(os_count(os) == 0) + return st_SUCCESS; + + if(mysql_ping(data->conn) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: connection to database lost"); + return st_FAILED; + } + + if(data->txn) { + if(mysql_query(data->conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE") != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql transaction setup failed: %s", mysql_error(data->conn)); + return st_FAILED; + } + + if(mysql_query(data->conn, "BEGIN") != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql transaction begin failed: %s", mysql_error(data->conn)); + return st_FAILED; + } + } + + if(_st_mysql_put_guts(drv, type, owner, os) != st_SUCCESS) { + if(data->txn) + mysql_query(data->conn, "ROLLBACK"); + return st_FAILED; + } + + if(data->txn) + if(mysql_query(data->conn, "COMMIT") != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql transaction commit failed: %s", mysql_error(data->conn)); + mysql_query(data->conn, "ROLLBACK"); + return st_FAILED; + } + + return st_SUCCESS; +} + +static st_ret_t _st_mysql_get(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t *os) { + drvdata_t data = (drvdata_t) drv->private; + char *cond, *buf = NULL; + int buflen = 0; + MYSQL_RES *res; + int ntuples, nfields, i, j; + MYSQL_FIELD *fields; + MYSQL_ROW tuple; + unsigned long *lengths; + os_object_t o; + char *val; + os_type_t ot; + int ival; + char tbuf[128]; + + if(mysql_ping(data->conn) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: connection to database lost"); + return st_FAILED; + } + + if(data->prefix != NULL) { + snprintf(tbuf, sizeof(tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + cond = _st_mysql_convert_filter(drv, owner, filter); + log_debug(ZONE, "generated filter: %s", cond); + + MYSQL_SAFE(buf, strlen(type) + strlen(cond) + 50, buflen); + sprintf(buf, "SELECT * FROM `%s` WHERE %s ORDER BY `object-sequence`", type, cond); + free(cond); + + log_debug(ZONE, "prepared sql: %s", buf); + + if(mysql_query(data->conn, buf) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql select failed: %s", mysql_error(data->conn)); + free(buf); + return st_FAILED; + } + free(buf); + + res = mysql_store_result(data->conn); + if(res == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql result retrieval failed: %s", mysql_error(data->conn)); + return st_FAILED; + } + + ntuples = mysql_num_rows(res); + if(ntuples == 0) { + mysql_free_result(res); + return st_NOTFOUND; + } + + log_debug(ZONE, "%d tuples returned", ntuples); + + nfields = mysql_num_fields(res); + + if(nfields == 0) { + log_debug(ZONE, "weird, tuples were returned but no fields *shrug*"); + mysql_free_result(res); + return st_NOTFOUND; + } + + fields = mysql_fetch_fields(res); + + *os = os_new(); + + for(i = 0; i < ntuples; i++) { + o = os_object_new(*os); + + if((tuple = mysql_fetch_row(res)) == NULL) + break; + + for(j = 0; j < nfields; j++) { + if(strcmp(fields[j].name, "collection-owner") == 0 || strcmp(fields[j].name, "object-sequence") == 0) + continue; + + if(tuple[j] == NULL) + continue; + + lengths = mysql_fetch_lengths(res); + + switch(fields[j].type) { + case FIELD_TYPE_TINY: /* tinyint */ + ot = os_type_BOOLEAN; + break; + + case FIELD_TYPE_LONG: /* integer */ + ot = os_type_INTEGER; + break; + + case FIELD_TYPE_BLOB: /* text */ + case FIELD_TYPE_VAR_STRING: /* varchar */ + ot = os_type_STRING; + break; + + default: + log_debug(ZONE, "unknown field type %d, ignoring it", fields[j].type); + continue; + } + + val = tuple[j]; + + switch(ot) { + case os_type_BOOLEAN: + ival = (val[0] == '0') ? 0 : 1; + os_object_put(o, fields[j].name, &ival, ot); + break; + + case os_type_INTEGER: + ival = atoi(val); + os_object_put(o, fields[j].name, &ival, ot); + break; + + case os_type_STRING: + os_object_put(o, fields[j].name, val, os_type_STRING); + break; + + case os_type_NAD: + case os_type_UNKNOWN: + break; + } + } + } + + mysql_free_result(res); + + return st_SUCCESS; +} + +static st_ret_t _st_mysql_delete(st_driver_t drv, const char *type, const char *owner, const char *filter) { + drvdata_t data = (drvdata_t) drv->private; + char *cond, *buf = NULL; + int buflen = 0; + char tbuf[128]; + + if(mysql_ping(data->conn) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: connection to database lost"); + return st_FAILED; + } + + if(data->prefix != NULL) { + snprintf(tbuf, sizeof(tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + cond = _st_mysql_convert_filter(drv, owner, filter); + log_debug(ZONE, "generated filter: %s", cond); + + MYSQL_SAFE(buf, strlen(type) + strlen(cond) + 19, buflen); + sprintf(buf, "DELETE FROM `%s` WHERE %s", type, cond); + free(cond); + + log_debug(ZONE, "prepared sql: %s", buf); + + if(mysql_query(data->conn, buf) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql delete failed: %s", mysql_error(data->conn)); + free(buf); + return st_FAILED; + } + free(buf); + + return st_SUCCESS; +} + +static st_ret_t _st_mysql_replace(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t os) { + drvdata_t data = (drvdata_t) drv->private; + + if(mysql_ping(data->conn) != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: connection to database lost"); + return st_FAILED; + } + + if(data->txn) { + if(mysql_query(data->conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE") != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql transaction setup failed: %s", mysql_error(data->conn)); + return st_FAILED; + } + + if(mysql_query(data->conn, "BEGIN") != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql transaction begin failed: %s", mysql_error(data->conn)); + return st_FAILED; + } + } + + if(_st_mysql_delete(drv, type, owner, filter) == st_FAILED) { + if(data->txn) + mysql_query(data->conn, "ROLLBACK"); + return st_FAILED; + } + + if(_st_mysql_put_guts(drv, type, owner, os) == st_FAILED) { + if(data->txn) + mysql_query(data->conn, "ROLLBACK"); + return st_FAILED; + } + + if(data->txn) + if(mysql_query(data->conn, "COMMIT") != 0) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: sql transaction commit failed: %s", mysql_error(data->conn)); + mysql_query(data->conn, "ROLLBACK"); + return st_FAILED; + } + + return st_SUCCESS; +} + +static void _st_mysql_free(st_driver_t drv) { + drvdata_t data = (drvdata_t) drv->private; + + mysql_close(data->conn); + + xhash_free(data->filters); + + free(data); +} + +st_ret_t st_mysql_init(st_driver_t drv) { + char *host, *port, *dbname, *user, *pass; + MYSQL *conn; + drvdata_t data; + + host = config_get_one(drv->st->sm->config, "storage.mysql.host", 0); + port = config_get_one(drv->st->sm->config, "storage.mysql.port", 0); + dbname = config_get_one(drv->st->sm->config, "storage.mysql.dbname", 0); + user = config_get_one(drv->st->sm->config, "storage.mysql.user", 0); + pass = config_get_one(drv->st->sm->config, "storage.mysql.pass", 0); + + if(host == NULL || port == NULL || dbname == NULL || user == NULL || pass == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: invalid driver config"); + return st_FAILED; + } + + conn = mysql_init(NULL); + if(conn == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: unable to allocate database connection state"); + return st_FAILED; + } + +#if MYSQL_VERSION_ID > 32349 + /* Set mysql_options for client ver 3.23.50 or later (workaround for mysql bug in 3.23.49) */ + mysql_options(conn, MYSQL_READ_DEFAULT_GROUP, "jabberd"); +#endif + + /* connect with CLIENT_INTERACTIVE to get a (possibly) higher timeout value than default */ + if(mysql_real_connect(conn, host, user, pass, dbname, atoi(port), NULL, CLIENT_INTERACTIVE) == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "mysql: connection to database failed: %s", mysql_error(conn)); + mysql_close(conn); + return st_FAILED; + } + + /* Set reconnect flag to 1 (set to 0 by default from mysql 5 on) */ + conn->reconnect = 1; + + data = (drvdata_t) malloc(sizeof(struct drvdata_st)); + memset(data, 0, sizeof(struct drvdata_st)); + + data->conn = conn; + + data->filters = xhash_new(17); + + if(config_get_one(drv->st->sm->config, "storage.mysql.transactions", 0) != NULL) + data->txn = 1; + else + log_write(drv->st->sm->log, LOG_WARNING, "mysql: transactions disabled"); + + data->prefix = config_get_one(drv->st->sm->config, "storage.mysql.prefix", 0); + + drv->private = (void *) data; + + drv->add_type = _st_mysql_add_type; + drv->put = _st_mysql_put; + drv->get = _st_mysql_get; + drv->delete = _st_mysql_delete; + drv->replace = _st_mysql_replace; + drv->free = _st_mysql_free; + + return st_SUCCESS; +} + +#endif diff --git a/sm/storage_oracle.c b/sm/storage_oracle.c new file mode 100644 index 00000000..ddb36409 --- /dev/null +++ b/sm/storage_oracle.c @@ -0,0 +1,951 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file sm/storage_oracle.c + * @brief oracle storage module + * @author Nathan Christiansen + * $Date: 2005/06/02 04:54:33 $ + * $Revision: 1.2 $ + */ + +#include "sm.h" + +#ifdef STORAGE_ORACLE + +#include +#include + +/** internal structure, holds our data */ +typedef struct OracleDriver +{ + OCIEnv *ociEnvironment; + OCIError *ociError; + OCISvcCtx *ociService; + OCIStmt *ociStatement; + OCIDefine *ociDefine; + OCIBind *ociBind; + xht filters; + char *prefix; + char *svUser; + char *svPass; +} *OracleDriverPointer; + +#define BLOCKSIZE (1024) + +/** internal: do and return the math and ensure it gets realloc'd */ +static int _st_oracle_realloc(void **oblocks, int len) +{ + void *nblocks; + int nlen; + + /* round up to standard block sizes */ + nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE; + + /* keep trying till we get it */ + while((nblocks = realloc(*oblocks, nlen)) == NULL) sleep(1); + *oblocks = nblocks; + return nlen; +} + +/** this is the safety check used to make sure there's always enough mem */ +#define ORACLE_SAFE(blocks, size, len) if((size) > len) len = _st_oracle_realloc((void**)&(blocks),(size)); + +int checkOCIError(st_driver_t drv, char *szDoing, OCIError *m_ociError, sword nStatus) +{ + text txtErrorBuffer[512]; + ub4 nErrorCode; + + switch (nStatus) + { + case OCI_SUCCESS: + break; + case OCI_SUCCESS_WITH_INFO: + log_write(drv->st->sm->log, LOG_ERR, "(%s) Error - OCI_SUCCESS_WITH_INFO\n", szDoing); + break; + case OCI_NEED_DATA: + log_write(drv->st->sm->log, LOG_ERR, "(%s) Error - OCI_NEED_DATA\n", szDoing); + break; + case OCI_NO_DATA: + log_write(drv->st->sm->log, LOG_ERR, "(%s) Error - OCI_NODATA\n", szDoing); + break; + case OCI_ERROR: + OCIErrorGet(m_ociError, (ub4) 1, (text *) NULL, &nErrorCode, txtErrorBuffer, (ub4) sizeof(txtErrorBuffer), OCI_HTYPE_ERROR); + log_write(drv->st->sm->log, LOG_ERR, "(%s) Error - %s\n", szDoing, txtErrorBuffer); + break; + case OCI_INVALID_HANDLE: + log_write(drv->st->sm->log, LOG_ERR, "(%s) Error - OCI_INVALID_HANDLE\n", szDoing); + break; + case OCI_STILL_EXECUTING: + log_write(drv->st->sm->log, LOG_ERR, "(%s) Error - OCI_STILL_EXECUTE\n", szDoing); + break; + default: + break; + } + + return nStatus; +} + +int oracle_escape_string(char *dest, int dest_length, char *src, int src_length) +{ + int result = 0; + /* src is not null ended */ + char *src_end = src + src_length; + char *dest_end = dest + dest_length - 1; + + while (src < src_end) + { + if (dest < dest_end) + { + static const char ESCAPED_STR[] = "'&"; + + if (strchr(ESCAPED_STR, *src) != NULL) + { + if (dest + 2 < dest_end) + { + *dest++ = '\''; + *dest++ = *src; + src++; + } + else + { + result = -1; + break; + } + } + else + { + *dest++ = *src++; + } + } + else + { + result = -1; + break; + } + } + *dest = '\0'; + + return result; +} + + + +static int oracle_ping(st_driver_t drv) +{ + OracleDriverPointer odpOracleDriver = (OracleDriverPointer)drv->private; + + // Prepare the check statement + int nResultCode = OCIStmtPrepare(odpOracleDriver->ociStatement, odpOracleDriver->ociError, + "select sysdate from dual", (ub4) 24, OCI_NTV_SYNTAX, + OCI_DEFAULT); + + // This is the real check + nResultCode = OCIStmtExecute(odpOracleDriver->ociService, odpOracleDriver->ociStatement, odpOracleDriver->ociError, + (ub4) 0, (ub4) 0, + (CONST OCISnapshot *) NULL, (OCISnapshot *) NULL, + OCI_DESCRIBE_ONLY ); + + + // If there was an error... + if (nResultCode != 0) + { + char szErrorBuffer[250]; + char *svHost, *svUser, *svPass; + + OCIErrorGet((dvoid *)odpOracleDriver->ociError, (ub4) 1, (text *) NULL, &nResultCode, szErrorBuffer, (ub4) sizeof(szErrorBuffer), OCI_HTYPE_ERROR); + log_write(drv->st->sm->log, LOG_ERR, "storage_oracle.c (oracle_ping): %s", szErrorBuffer); + + // Obtain user configuration + svHost = config_get_one(drv->st->sm->config, "storage.oracle.host", 0); + svUser = config_get_one(drv->st->sm->config, "storage.oracle.user", 0); + svPass = config_get_one(drv->st->sm->config, "storage.oracle.pass", 0); + + // Logon to the database + nResultCode = OCILogon((dvoid *)odpOracleDriver->ociEnvironment, (dvoid *)odpOracleDriver->ociError, &(odpOracleDriver->ociService), svUser, strlen(svUser), svPass, strlen(svPass), svHost, strlen(svHost)); + + + if (nResultCode != 0) + { + OCIErrorGet((dvoid *)odpOracleDriver->ociError, (ub4) 1, (text *) NULL, &nResultCode, szErrorBuffer, (ub4) sizeof(szErrorBuffer), OCI_HTYPE_ERROR); + log_write(drv->st->sm->log, LOG_ERR, "storage_oracle.c (oracle_ping): %s", szErrorBuffer); + } + } + + return nResultCode; +} + + + +static void _st_oracle_convert_filter_recursive(st_filter_t f, char **buf, int *buflen, int *nbuf) +{ + st_filter_t scan; + + switch(f->type) + { + case st_filter_type_PAIR: + ORACLE_SAFE((*buf), *buflen + 12, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( \"%s\" = \'%s\' ) ", f->key, f->val); + + break; + + case st_filter_type_AND: + ORACLE_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( "); + + for(scan = f->sub; scan != NULL; scan = scan->next) + { + _st_oracle_convert_filter_recursive(scan, buf, buflen, nbuf); + + if(scan->next != NULL) + { + ORACLE_SAFE((*buf), *buflen + 4, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "AND "); + } + } + + ORACLE_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), ") "); + + return; + + case st_filter_type_OR: + ORACLE_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( "); + + for(scan = f->sub; scan != NULL; scan = scan->next) + { + _st_oracle_convert_filter_recursive(scan, buf, buflen, nbuf); + + if(scan->next != NULL) + { + ORACLE_SAFE((*buf), *buflen + 3, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "OR "); + } + } + + ORACLE_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), ") "); + + return; + + case st_filter_type_NOT: + ORACLE_SAFE((*buf), *buflen + 6, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( NOT "); + + _st_oracle_convert_filter_recursive(f->sub, buf, buflen, nbuf); + + ORACLE_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), ") "); + + return; + } +} + +static char *_st_oracle_convert_filter(st_driver_t drv, char *owner, char *filter) +{ + OracleDriverPointer data = (OracleDriverPointer) drv->private; + char *buf = NULL, *sbuf = NULL, *cfilter; + int buflen = 0, nbuf = 0, fbuf; + st_filter_t f; + + ORACLE_SAFE(buf, 24 + strlen(owner), buflen); + + nbuf = sprintf(buf, "\"collection-owner\" = '%s'", owner); + + sbuf = xhash_get(data->filters, filter); + if(sbuf != NULL) + { + ORACLE_SAFE(buf, buflen + strlen(sbuf) + 7, buflen); + nbuf += sprintf(&buf[nbuf], " AND %s", sbuf); + return buf; + } + + cfilter = pstrdup(xhash_pool(data->filters), filter); + + f = storage_filter(filter); + if(f == NULL) + { + return buf; + } + + ORACLE_SAFE(buf, buflen + 5, buflen); + nbuf += sprintf(&buf[nbuf], " AND "); + + fbuf = nbuf; + + _st_oracle_convert_filter_recursive(f, &buf, &buflen, &nbuf); + + xhash_put(data->filters, cfilter, pstrdup(xhash_pool(data->filters), &buf[fbuf])); + + pool_free(f->p); + + return buf; +} + +static st_ret_t _st_oracle_add_type(st_driver_t drv, char *type) +{ + return st_SUCCESS; +} + + +static st_ret_t _st_oracle_put_guts(st_driver_t drv, char *type, char *owner, os_t os) +{ + static const char STR_PREFIX[] = "STR"; + static const char NAD_PREFIX[] = "NAD"; + + OracleDriverPointer data = (OracleDriverPointer) drv->private; + char *left = NULL, *right = NULL; + int lleft = 0, lright = 0, nleft, nright; + os_object_t o; + char *key = NULL, *cval = NULL; + int cval_len; + dvoid *val = NULL; + os_type_t ot; + char *xml = NULL; + int xlen; + char tbuf[128]; + int nResultCode = 0; + + if(os_count(os) == 0) + { + return st_SUCCESS; + } + + if(data->prefix != NULL) + { + snprintf(tbuf, sizeof(tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + if(os_iter_first(os)) + { + do + { + ORACLE_SAFE(left, strlen(type) + 36, lleft); + nleft = sprintf(left, "INSERT INTO \"%s\" ( \"collection-owner\"", type); + + ORACLE_SAFE(right, strlen(owner) + 15, lright); + nright = sprintf(right, " ) VALUES ( '%s'", owner); + + o = os_iter_object(os); + + if(os_object_iter_first(o)) + { + do + { + os_object_iter_get(o, &key, &val, &ot); + + switch(ot) + { + case os_type_BOOLEAN: + cval = val ? strdup("1") : strdup("0"); + break; + + case os_type_INTEGER: + cval = (char *) malloc(sizeof(char) * 20); + sprintf(cval, "%d", (int) val); + strlen(cval); + break; + + case os_type_STRING: + /* Ensure that we have enough space for an escaped string. */ + cval_len = (strlen((char *) val) * 2) + 1; + cval = (char *) malloc(cval_len); + oracle_escape_string(cval , cval_len, (char *) val, strlen(val)); + break; + + /* !!! might not be a good idea to mark nads this way */ + case os_type_NAD: + nad_print((nad_t) val, 0, &xml, &xlen); + /* Ensure that we have enough space for an escaped string. */ + cval_len = (xlen * 2) + sizeof (NAD_PREFIX); + cval = (char *) malloc(cval_len); + memcpy(cval, NAD_PREFIX, sizeof (NAD_PREFIX) - 1); + oracle_escape_string(cval + sizeof (NAD_PREFIX) - 1, + cval_len - sizeof (NAD_PREFIX) + 1, (char *) xml, xlen); + break; + } + + log_debug(ZONE, "key %s val %s", key, cval); + + ORACLE_SAFE(left, lleft + strlen(key) + 4, lleft); + nleft += sprintf(&left[nleft], ", \"%s\"", key); + + ORACLE_SAFE(right, lright + strlen(cval) + 4, lright); + nright += sprintf(&right[nright], ", '%s'", cval); + + free(cval); + } while(os_object_iter_next(o)); + + ORACLE_SAFE(left, lleft + strlen(right) + 2, lleft); + sprintf(&left[nleft], "%s )", right); + + log_debug(ZONE, "_st_oracle_put_guts: Generated SQL: %s", left); + + nResultCode = checkOCIError(drv, "oracle_put_guts: Prepare", data->ociError, OCIStmtPrepare(data->ociStatement, data->ociError, left, + (ub4) strlen(left), OCI_NTV_SYNTAX, OCI_DEFAULT)); + + if (nResultCode != 0) + { + free(left); + free(right); + return st_FAILED; + } + + nResultCode = checkOCIError(drv, "oracle_put_guts: Execute", data->ociError, OCIStmtExecute(data->ociService, data->ociStatement, + data->ociError, (ub4) 1, (ub4) 0, + (CONST OCISnapshot *) NULL, (OCISnapshot *) NULL, + OCI_DEFAULT | OCI_COMMIT_ON_SUCCESS)); + + if (nResultCode != 0) + { + free(left); + free(right); + return st_FAILED; + } + } + } while(os_iter_next(os)); + } + + free(left); + free(right); + + return st_SUCCESS; +} + +static st_ret_t _st_oracle_put(st_driver_t drv, char *type, char *owner, os_t os) +{ + + if( !owner ) { + log_debug(ZONE,"_st_oracle_put: owner is null"); + return st_FAILED; + } + + if(os_count(os) == 0) + { + return st_SUCCESS; + } + + if(oracle_ping(drv) != 0) + { + log_write(drv->st->sm->log, LOG_ERR, "oracle: connection to database lost"); + return st_FAILED; + } + + if(_st_oracle_put_guts(drv, type, owner, os) != st_SUCCESS) + { + return st_FAILED; + } + + return st_SUCCESS; +} + +static st_ret_t _st_oracle_get(st_driver_t drv, char *a_szType, char *owner, char *filter, os_t *os) +{ + OracleDriverPointer data = (OracleDriverPointer) drv->private; + os_object_t o; + os_type_t ot; + char szBuffer[128]; + char *szWhereClause = NULL; + char *szQuery = NULL; + int nQueryLength = 0; + int nResultCode = 0; + int nNumberOfFields = 0; + int nIndex = 0; + + if( !owner ) { + log_debug(ZONE,"_st_oracle_get: owner is null"); + return st_FAILED; + } + + if(oracle_ping(drv) != 0) + { + log_write(drv->st->sm->log, LOG_ERR, "_st_oracle_get: Connection to database lost!"); + return st_FAILED; + } + + if(data->prefix != NULL) + { + snprintf(szBuffer, sizeof(szBuffer), "%s%s", data->prefix, a_szType); + a_szType = szBuffer; + } + + szWhereClause = _st_oracle_convert_filter(drv, owner, filter); + log_debug(ZONE, "_st_oracle_get: Generated Filter: %s", szWhereClause); + + ORACLE_SAFE(szQuery, strlen(a_szType) + strlen(szWhereClause) + 50, nQueryLength); + sprintf(szQuery, "SELECT * FROM \"%s\" WHERE %s ORDER BY \"object-sequence\"", a_szType, szWhereClause); + free(szWhereClause); + + log_debug(ZONE, "_st_oracle_get: Prepared SQL: %s", szQuery); + + nResultCode = checkOCIError(drv, "_st_oracle_get: Prepare Statement", data->ociError, OCIStmtPrepare(data->ociStatement, data->ociError, + szQuery, (ub4)strlen(szQuery), OCI_NTV_SYNTAX, + OCI_DEFAULT)); + + if (nResultCode != 0) + { + free(szQuery); + return st_FAILED; + } + + nResultCode = checkOCIError(drv, "_st_oracle_get: Statement Describe", data->ociError, OCIStmtExecute(data->ociService, + data->ociStatement, data->ociError, (ub4)0, + (ub4)0, (CONST OCISnapshot *)NULL, + (OCISnapshot *)NULL, OCI_DESCRIBE_ONLY)); + + if (nResultCode != 0) + { + free(szQuery); + return st_FAILED; + } + + free(szQuery); + + nResultCode = OCI_SUCCESS; + + checkOCIError(drv, "_st_oracle_get: Get Field Count", data->ociError, OCIAttrGet(data->ociStatement, OCI_HTYPE_STMT, + (dvoid *)&nNumberOfFields, (ub4 *)NULL, OCI_ATTR_PARAM_COUNT, + data->ociError)); + if (nNumberOfFields == 0) + { + return st_NOTFOUND; + } + else + { + /* + * TODO: Handle memory better. + * The DDL for the "vcard" table has 21 fields. The following implementation allocates 82K for 21 fields. + */ + OCIDefine *arrFields[nNumberOfFields]; + char arrszFieldData[nNumberOfFields][4001]; /* Size each field for the maximum VARCHAR2 size + terminating null */ + char arrszFieldName[nNumberOfFields][255]; + ub2 arrnFieldType[nNumberOfFields]; + sb2 arrnFieldIndicator[nNumberOfFields]; + int arrnFieldSize[nNumberOfFields]; + char *svFieldName; + int nNameSize; + int nIntValue; + nad_t nad; + + + for (nIndex = 0; nIndex < nNumberOfFields; nIndex++) + { + arrFields[nIndex] = NULL; + + checkOCIError(drv, "_st_oracle_get: Get Parameter", data->ociError, OCIParamGet(data->ociStatement, OCI_HTYPE_STMT, data->ociError, + (dvoid **) &arrFields[nIndex], (ub4) (nIndex + 1))); + + checkOCIError(drv, "_st_oracle_get: Get Field Name", data->ociError, OCIAttrGet(arrFields[nIndex], OCI_DTYPE_PARAM, + (dvoid *) &svFieldName, &nNameSize, + OCI_ATTR_NAME, data->ociError)); + strncpy(arrszFieldName[nIndex], svFieldName, nNameSize); + arrszFieldName[nIndex][nNameSize] = '\0'; + + arrnFieldType[nIndex] = 0; + checkOCIError(drv, "_st_oracle_get: Get Field Type", data->ociError, OCIAttrGet(arrFields[nIndex], OCI_DTYPE_PARAM, + (dvoid *) &arrnFieldType[nIndex], (ub4 *) NULL, + (ub4) OCI_ATTR_DATA_TYPE, data->ociError)); + + checkOCIError(drv, "_st_oracle_get: Get Field Size", data->ociError, OCIAttrGet(arrFields[nIndex], OCI_DTYPE_PARAM, + (dvoid *) &arrnFieldSize[nIndex], (ub4 *) NULL, + (ub4) OCI_ATTR_DATA_SIZE, data->ociError)); + if (arrnFieldSize[nIndex] > 4000 || arrnFieldSize[nIndex] < 1) + { + arrnFieldSize[nIndex] = 4000; + } + + checkOCIError(drv, "_st_oracle_get: Define String", data->ociError, OCIDefineByPos(data->ociStatement, &arrFields[nIndex], + data->ociError, (nIndex + 1), (dvoid *)&arrszFieldData[nIndex], + 4000, SQLT_STR, &arrnFieldIndicator[nIndex], + (ub2 *) 0, (ub2 *) 0, OCI_DEFAULT)); + } + + + nResultCode = OCIStmtExecute(data->ociService, data->ociStatement, data->ociError, (ub4) 1, (ub4) 0, (CONST OCISnapshot *) NULL, + (OCISnapshot *) NULL, OCI_DEFAULT); + + if (nResultCode == OCI_SUCCESS || nResultCode == OCI_SUCCESS_WITH_INFO) + { + for (nIndex = 0; nIndex < nNumberOfFields; nIndex++) + { + if (arrnFieldIndicator[nIndex] == -1) + { + arrszFieldData[nIndex][0] = '\0'; + } + } + } + else if (nResultCode != OCI_NO_DATA) + { + checkOCIError(drv, "_st_oracle_get: Execute Statement", data->ociError, nResultCode); + return st_FAILED; + } + + if (nResultCode == OCI_NO_DATA) + { + return st_NOTFOUND; + } + + *os = os_new(); + + while (nResultCode != OCI_NO_DATA) + { + o = os_object_new(*os); + + for (nIndex = 0; nIndex < nNumberOfFields; nIndex++) + { + if(strcmp(arrszFieldName[nIndex], "collection-owner") == 0 || strcmp(arrszFieldName[nIndex], "object-sequence") == 0) + { + continue; + } + + if (arrszFieldData[nIndex][0] == '\0') + { + continue; + } + + switch(arrnFieldType[nIndex]) + { + case SQLT_CHR: /* VARCHAR2, VARCHAR, CHAR_VARYING, CHARACTER_VARYING, NVARCHAR2, + * NCHAR_VARYING, NATIONAL_CHAR_VARYING, or NATIONAL_CHARACTER_VARYING field. */ + if (arrnFieldSize[nIndex] > 2) + { + log_debug(ZONE, "Field %s is Field Type SQLT_CHR of Size %d, setting os_type_STRING", arrszFieldName[nIndex], arrnFieldSize[nIndex]); + ot = os_type_STRING; + } + else + { + log_debug(ZONE, "Field %s is Field Type SQLT_CHR of Size %d, setting os_type_BOOLEAN", arrszFieldName[nIndex], arrnFieldSize[nIndex]); + ot = os_type_BOOLEAN; + } + break; + + case SQLT_AFC: /* CHAR, CHARACTER, NATIONAL_CHAR, NATIONAL_CHARACTER, or NCHAR field. */ + if (arrnFieldSize[nIndex] > 2) + { + log_debug(ZONE, "Field %s is Field Type SQLT_AFC of Size %d, setting os_type_STRING", arrszFieldName[nIndex], arrnFieldSize[nIndex]); + ot = os_type_STRING; + } + else + { + log_debug(ZONE, "Field %s is Field Type SQLT_AFC of Size %d, setting os_type_BOOLEAN", arrszFieldName[nIndex], arrnFieldSize[nIndex]); + ot = os_type_BOOLEAN; + } + break; + + case SQLT_NUM: /* INT, REAL, NUMERIC, DOUBLE_PRECISION, SMALLINT, FLOAT, DECIMAL, NUMBER, or INTEGER field. */ + log_debug(ZONE, "Field %s is Field Type SQLT_NUM of Size %d", arrszFieldName[nIndex], arrnFieldSize[nIndex]); + ot = os_type_INTEGER; + break; + + default: + log_debug(ZONE, "Unknown field type %d, for column %s ignoring it", arrnFieldType[nIndex], arrszFieldName[nIndex]); + continue; + } + + switch(ot) + { + case os_type_BOOLEAN: + nIntValue = (arrszFieldData[nIndex][0] == '0') ? 0 : 1; + os_object_put(o, arrszFieldName[nIndex], &nIntValue, ot); + break; + + case os_type_INTEGER: + nIntValue = atoi(arrszFieldData[nIndex]); + os_object_put(o, arrszFieldName[nIndex], &nIntValue, ot); + break; + + case os_type_STRING: + if(strlen(arrszFieldData[nIndex]) >= 3 && strncmp(arrszFieldData[nIndex], "NAD", 3) == 0) + { + if(strlen(arrszFieldData[nIndex]) == 3) + { + log_write(drv->st->sm->log, LOG_ERR, "Found XML cell with no XML content; table=%s, owner=%s", a_szType, owner); + os_free(*os); + return st_FAILED; + } + + nad = nad_parse(drv->st->sm->router->nad_cache, &arrszFieldData[nIndex][3], strlen(arrszFieldData[nIndex]) - 3); + if(nad == NULL) + { + log_write(drv->st->sm->log, LOG_ERR, "Found XML cell with unparseable XML content; table=%s, owner=%s", a_szType, owner); + log_write(drv->st->sm->log, LOG_ERR, "Unparseable Content: %s", &arrszFieldData[nIndex][3]); + os_free(*os); + return st_FAILED; + } + + os_object_put(o, arrszFieldName[nIndex], nad, os_type_NAD); + } + else + { + int offset; + + if(strlen(arrszFieldData[nIndex]) >= 3 && strncmp(arrszFieldData[nIndex], "STR", 3) == 0) + { + offset = 3; + } + else + { + offset = 0; + } + + os_object_put(o, arrszFieldName[nIndex], arrszFieldData[nIndex + offset], os_type_STRING); + } + break; + + case os_type_NAD: + break; + } + } + + log_debug(ZONE, "Get Next Row."); + nResultCode = OCIStmtFetch2(data->ociStatement, data->ociError, 1, OCI_DEFAULT, 0, OCI_DEFAULT); + + if (nResultCode == OCI_SUCCESS || nResultCode == OCI_SUCCESS_WITH_INFO) + { + for (nIndex = 0; nIndex < nNumberOfFields; nIndex++) + { + if (arrnFieldIndicator[nIndex] == -1) + { + arrszFieldData[nIndex][0] = '\0'; + } + } + } + else if (nResultCode != OCI_NO_DATA) + { + checkOCIError(drv, "_st_oracle_get: Fetch Next Row", data->ociError, nResultCode); + // If we get a database error exit the while loop. This probably should return st_FAILED here. + break; + } + } + } + + return st_SUCCESS; +} + +static st_ret_t _st_oracle_delete(st_driver_t drv, char *type, char *owner, char *filter) +{ + OracleDriverPointer data = (OracleDriverPointer) drv->private; + char *cond, *buf = NULL; + int buflen = 0; + int nResultCode = 0; + char tbuf[128]; + + if(oracle_ping(drv) != 0) + { + log_write(drv->st->sm->log, LOG_ERR, "oracle: Connection to database lost"); + return st_FAILED; + } + + if(data->prefix != NULL) + { + snprintf(tbuf, sizeof(tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + cond = _st_oracle_convert_filter(drv, owner, filter); + log_debug(ZONE, "oracle: Generated filter: %s", cond); + + ORACLE_SAFE(buf, strlen(type) + strlen(cond) + 19, buflen); + sprintf(buf, "DELETE FROM \"%s\" WHERE %s", type, cond); + free(cond); + + log_debug(ZONE, "_st_oracle_delete: Prepared SQL: %s", buf); + + nResultCode = checkOCIError(drv, "_st_oracle_delete: Prepare", data->ociError, OCIStmtPrepare(data->ociStatement, data->ociError, buf, + (ub4) strlen(buf), OCI_NTV_SYNTAX, OCI_DEFAULT)); + + if (nResultCode != 0) + { + free(buf); + return st_FAILED; + } + + log_debug(ZONE, "_st_oracle_delete: Executing Delete."); + + nResultCode = checkOCIError(drv, "_st_oracle_delete: Execute", data->ociError, OCIStmtExecute(data->ociService, data->ociStatement, + data->ociError, (ub4) 1, (ub4) 0, (CONST OCISnapshot *) NULL, + (OCISnapshot *) NULL, OCI_DEFAULT | OCI_COMMIT_ON_SUCCESS)); + free(buf); + + log_debug(ZONE, "Result query: %d",nResultCode); + + if(nResultCode != 0) + { + return st_FAILED; + } + + return st_SUCCESS; +} + +static st_ret_t _st_oracle_replace(st_driver_t drv, char *type, char *owner, char *filter, os_t os) +{ + if(oracle_ping(drv) != 0) + { + log_write(drv->st->sm->log, LOG_ERR, "oracle: connection to database lost"); + return st_FAILED; + } + + if(_st_oracle_delete(drv, type, owner, filter) == st_FAILED) + { + return st_FAILED; + } + + if(_st_oracle_put_guts(drv, type, owner, os) == st_FAILED) + { + return st_FAILED; + } + + return st_SUCCESS; +} + +static void _st_oracle_free(st_driver_t drv) { + OracleDriverPointer data = (OracleDriverPointer) drv->private; + + OCILogoff(data->ociService, data->ociError); + OCIHandleFree((dvoid *) data->ociStatement, OCI_HTYPE_STMT); + OCIHandleFree((dvoid *) data->ociService, OCI_HTYPE_SVCCTX); + OCIHandleFree((dvoid *) data->ociError, OCI_HTYPE_ERROR); + OCIHandleFree((dvoid *) data->ociEnvironment, OCI_HTYPE_ENV); + + xhash_free(data->filters); + + free(data->prefix); + + free(data); +} + +st_ret_t st_oracle_init(st_driver_t drv) + { + int nResultCode; + char *svHost, *svUser, *svPass; + OCIEnv *ociEnvironment; + OCIError *ociError; + OCISvcCtx *ociService; + OCIStmt *ociStatement; + + OracleDriverPointer data; + + svHost = config_get_one(drv->st->sm->config, "storage.oracle.host", 0); + svUser = config_get_one(drv->st->sm->config, "storage.oracle.user", 0); + svPass = config_get_one(drv->st->sm->config, "storage.oracle.pass", 0); + + if(svHost == NULL || svUser == NULL || svPass == NULL) + { + log_write(drv->st->sm->log, LOG_ERR, "(st_oracle_init: ) Invalid driver config from XML file."); + return st_FAILED; + } + + /* Initialize OCI */ + nResultCode = OCIInitialize((ub4) OCI_DEFAULT, (dvoid *)0, (dvoid * (*)(dvoid *, size_t))0, (dvoid * (*)(dvoid *, dvoid *, size_t))0, (void (*)(dvoid *, dvoid *)) 0); + + if (nResultCode != 0) + { + log_write(drv->st->sm->log, LOG_ERR, "(st_oracle_init: ) Could not Initialize OCI (%d)", nResultCode); + return st_FAILED; + } + + /* Initialize evironment */ + nResultCode = OCIEnvInit((OCIEnv **) &ociEnvironment, OCI_DEFAULT, (size_t) 0, (dvoid **) 0); + + if (nResultCode != 0) + { + log_write(drv->st->sm->log, LOG_ERR, "(st_oracle_init: ) Could not Initialize OCI Environment (%d)", nResultCode); + return st_FAILED; + } + + /* Initialize handles */ + nResultCode = OCIHandleAlloc((dvoid *) ociEnvironment, (dvoid **) &ociError, OCI_HTYPE_ERROR, (size_t) 0, (dvoid **) 0); + + if (nResultCode != 0) + { + log_write(drv->st->sm->log, LOG_ERR, "(st_oracle_init: ) Could not create OCI Error object (%d)" , nResultCode); + nResultCode = OCIHandleFree((dvoid *) ociEnvironment, OCI_HTYPE_ENV); + return st_FAILED; + } + + nResultCode = checkOCIError(drv, "st_oracle_init: Allocate Service", ociError, OCIHandleAlloc((dvoid *) ociEnvironment, + (dvoid **)&ociService, OCI_HTYPE_SVCCTX, + (size_t)NULL, (dvoid **)NULL)); + if (nResultCode != 0) + { + nResultCode = OCIHandleFree((dvoid *) ociError, OCI_HTYPE_ERROR); + nResultCode = OCIHandleFree((dvoid *) ociEnvironment, OCI_HTYPE_ENV); + return st_FAILED; + } + + /* Connect to database server */ + nResultCode = checkOCIError(drv, "st_oracle_init: Connect to Server", ociError, OCILogon(ociEnvironment, ociError, &ociService, + svUser, strlen(svUser), svPass, strlen(svPass), + svHost, strlen(svHost))); + + if (nResultCode != 0) + { + nResultCode = OCIHandleFree((dvoid *) ociService, OCI_HTYPE_SVCCTX); + nResultCode = OCIHandleFree((dvoid *) ociError, OCI_HTYPE_ERROR); + nResultCode = OCIHandleFree((dvoid *) ociEnvironment, OCI_HTYPE_ENV); + return st_FAILED; + } + + /* Allocate and prepare SQL statement */ + nResultCode = checkOCIError(drv, "st_oracle_init: Allocate Statement", ociError, OCIHandleAlloc((dvoid *)ociEnvironment, + (dvoid **)&ociStatement, OCI_HTYPE_STMT, + (size_t)NULL, (dvoid **)NULL)); + + if (nResultCode != 0) + { + nResultCode = OCILogoff(ociService, ociError); + nResultCode = OCIHandleFree((dvoid *) ociService, OCI_HTYPE_SVCCTX); + nResultCode = OCIHandleFree((dvoid *) ociError, OCI_HTYPE_ERROR); + nResultCode = OCIHandleFree((dvoid *) ociEnvironment, OCI_HTYPE_ENV); + return st_FAILED; + } + + data = (OracleDriverPointer) malloc(sizeof(struct OracleDriver)); + memset(data, 0, sizeof(struct OracleDriver)); + + data->ociEnvironment = ociEnvironment; + data->ociError = ociError; + data->ociService = ociService; + data->ociStatement = ociStatement; + data->ociDefine = NULL; + data->ociBind = NULL; + data->svUser = svUser; + data->svPass = svPass; + + data->filters = xhash_new(17); + + data->prefix = config_get_one(drv->st->sm->config, "storage.oracle.prefix", 0); + + drv->private = (void *) data; + + drv->add_type = _st_oracle_add_type; + drv->put = _st_oracle_put; + drv->get = _st_oracle_get; + drv->delete = _st_oracle_delete; + drv->replace = _st_oracle_replace; + drv->free = _st_oracle_free; + + return st_SUCCESS; + } + +#endif + + diff --git a/sm/storage_pgsql.c b/sm/storage_pgsql.c new file mode 100644 index 00000000..d8a3a28f --- /dev/null +++ b/sm/storage_pgsql.c @@ -0,0 +1,642 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file sm/storage_pgsql.c + * @brief postgresql storage module + * @author Robert Norris + * $Date: 2005/06/02 04:48:25 $ + * $Revision: 1.25 $ + */ + +#include "sm.h" + +#ifdef STORAGE_PGSQL + +#include + +/** internal structure, holds our data */ +typedef struct drvdata_st { + PGconn *conn; + + char *prefix; + + xht filters; + + int txn; +} *drvdata_t; + +#define FALLBACK_BLOCKSIZE (4096) + +/** internal: do and return the math and ensure it gets realloc'd */ +static size_t _st_pgsql_realloc(char **oblocks, size_t len) { + void *nblocks; + size_t nlen; + static size_t block_size = 0; + + if (block_size == 0) { +#ifdef HAVE_GETPAGESIZE + block_size = getpagesize(); +#elif defined(_SC_PAGESIZE) + block_size = sysconf(_SC_PAGESIZE); +#elif defined(_SC_PAGE_SIZE) + block_size = sysconf(_SC_PAGE_SIZE); +#else + block_size = FALLBACK_BLOCKSIZE; +#endif + } + /* round up to standard block sizes */ + nlen = (((len-1)/block_size)+1)*block_size; + + /* keep trying till we get it */ + while((nblocks = realloc(*oblocks, nlen)) == NULL) sleep(1); + *oblocks = nblocks; + return nlen; +} + +/** this is the safety check used to make sure there's always enough mem */ +#define PGSQL_SAFE(blocks, size, len) if((size) >= len) len = _st_pgsql_realloc(&(blocks),(size + 1)); + +static void _st_pgsql_convert_filter_recursive(st_driver_t drv, st_filter_t f, char **buf, int *buflen, int *nbuf) { + st_filter_t scan; + int vlen; + char *cval; + + switch(f->type) { + case st_filter_type_PAIR: + /* do sql escaping for apostrophes */ + cval = (char *) malloc(sizeof(char) * ((strlen(f->val) * 2) + 1)); + vlen = PQescapeString(cval, f->val, strlen(f->val)); + + PGSQL_SAFE((*buf), *buflen + 12 + vlen - strlen(f->val), *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( \"%s\" = \'%s\' ) ", f->key, f->val); + free(cval); + + break; + + case st_filter_type_AND: + PGSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( "); + + for(scan = f->sub; scan != NULL; scan = scan->next) { + _st_pgsql_convert_filter_recursive(drv, scan, buf, buflen, nbuf); + + if(scan->next != NULL) { + PGSQL_SAFE((*buf), *buflen + 4, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "AND "); + } + } + + PGSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), ") "); + + return; + + case st_filter_type_OR: + PGSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( "); + + for(scan = f->sub; scan != NULL; scan = scan->next) { + _st_pgsql_convert_filter_recursive(drv, scan, buf, buflen, nbuf); + + if(scan->next != NULL) { + PGSQL_SAFE((*buf), *buflen + 3, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "OR "); + } + } + + PGSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), ") "); + + return; + + case st_filter_type_NOT: + PGSQL_SAFE((*buf), *buflen + 6, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), "( NOT "); + + _st_pgsql_convert_filter_recursive(drv, f->sub, buf, buflen, nbuf); + + PGSQL_SAFE((*buf), *buflen + 2, *buflen); + *nbuf += sprintf(&((*buf)[*nbuf]), ") "); + + return; + } +} + +static char *_st_pgsql_convert_filter(st_driver_t drv, const char *owner, const char *filter) { + drvdata_t data = (drvdata_t) drv->private; + char *buf = NULL, *sbuf = NULL, *cfilter; + int buflen = 0, nbuf = 0, fbuf; + st_filter_t f; + + PGSQL_SAFE(buf, 24 + strlen(owner), buflen); + + nbuf = sprintf(buf, "\"collection-owner\" = '%s'", owner); + + sbuf = xhash_get(data->filters, filter); + if(sbuf != NULL) { + PGSQL_SAFE(buf, buflen + strlen(sbuf) + 7, buflen); + nbuf += sprintf(&buf[nbuf], " AND %s", sbuf); + return buf; + } + + cfilter = pstrdup(xhash_pool(data->filters), filter); + + f = storage_filter(filter); + if(f == NULL) + return buf; + + PGSQL_SAFE(buf, buflen + 5, buflen); + nbuf += sprintf(&buf[nbuf], " AND "); + + fbuf = nbuf; + + _st_pgsql_convert_filter_recursive(drv, f, &buf, &buflen, &nbuf); + + xhash_put(data->filters, cfilter, pstrdup(xhash_pool(data->filters), &buf[fbuf])); + + pool_free(f->p); + + return buf; +} + +static st_ret_t _st_pgsql_add_type(st_driver_t drv, const char *type) { + return st_SUCCESS; +} + +static st_ret_t _st_pgsql_put_guts(st_driver_t drv, const char *type, const char *owner, os_t os) { + drvdata_t data = (drvdata_t) drv->private; + char *left = NULL, *right = NULL; + int lleft = 0, lright = 0, nleft, nright; + os_object_t o; + char *key, *cval = NULL; + void *val; + int vlen; + os_type_t ot; + char *xml; + int xlen; + PGresult *res; + char tbuf[128]; + + if(os_count(os) == 0) + return st_SUCCESS; + + if(data->prefix != NULL) { + snprintf(tbuf, sizeof(tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + if(os_iter_first(os)) + do { + PGSQL_SAFE(left, strlen(type) + 55, lleft); + nleft = sprintf(left, "INSERT INTO \"%s\" ( \"collection-owner\", \"object-sequence\"", type); + + PGSQL_SAFE(right, strlen(owner) + 43, lright); + nright = sprintf(right, " ) VALUES ( '%s', nextval('object-sequence')", owner); + + o = os_iter_object(os); + if(os_object_iter_first(o)) + do { + os_object_iter_get(o, &key, &val, &ot); + + switch(ot) { + case os_type_BOOLEAN: + cval = val ? strdup("t") : strdup("f"); + vlen = 1; + break; + + case os_type_INTEGER: + cval = (char *) malloc(sizeof(char) * 20); + sprintf(cval, "%d", (int) val); + vlen = strlen(cval); + break; + + case os_type_STRING: + cval = (char *) malloc(sizeof(char) * ((strlen((char *) val) * 2) + 1)); + vlen = PQescapeString(cval, (char *) val, strlen((char *) val)); + break; + + case os_type_NAD: + nad_print((nad_t) val, 0, &xml, &xlen); + cval = (char *) malloc(sizeof(char) * ((xlen * 2) + 4)); + vlen = PQescapeString(&cval[3], xml, xlen) + 3; + strncpy(cval, "NAD", 3); + break; + + case os_type_UNKNOWN: + break; + } + + log_debug(ZONE, "key %s val %s", key, cval); + + PGSQL_SAFE(left, lleft + strlen(key) + 4, lleft); + nleft += sprintf(&left[nleft], ", \"%s\"", key); + + PGSQL_SAFE(right, lright + strlen(cval) + 4, lright); + nright += sprintf(&right[nright], ", '%s'", cval); + + free(cval); + } while(os_object_iter_next(o)); + + PGSQL_SAFE(left, lleft + strlen(right) + 3, lleft); + sprintf(&left[nleft], "%s );", right); + + log_debug(ZONE, "prepared sql: %s", left); + + res = PQexec(data->conn, left); + + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(data->conn) != CONNECTION_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(data->conn); + res = PQexec(data->conn, left); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: sql insert failed: %s", PQresultErrorMessage(res)); + free(left); + free(right); + PQclear(res); + return st_FAILED; + } + + PQclear(res); + + } while(os_iter_next(os)); + + free(left); + free(right); + + return st_SUCCESS; +} + +static st_ret_t _st_pgsql_put(st_driver_t drv, const char *type, const char *owner, os_t os) { + drvdata_t data = (drvdata_t) drv->private; + PGresult *res; + + if(os_count(os) == 0) + return st_SUCCESS; + + if(data->txn) { + res = PQexec(data->conn, "BEGIN;"); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(data->conn) != CONNECTION_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(data->conn); + res = PQexec(data->conn, "BEGIN;"); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: sql transaction begin failed: %s", PQresultErrorMessage(res)); + PQclear(res); + return st_FAILED; + } + PQclear(res); + + res = PQexec(data->conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;"); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(data->conn) != CONNECTION_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(data->conn); + res = PQexec(data->conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;"); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: sql transaction setup failed: %s", PQresultErrorMessage(res)); + PQclear(res); + PQclear(PQexec(data->conn, "ROLLBACK;")); + return st_FAILED; + } + PQclear(res); + } + + if(_st_pgsql_put_guts(drv, type, owner, os) != st_SUCCESS) { + if(data->txn) + PQclear(PQexec(data->conn, "ROLLBACK;")); + return st_FAILED; + } + + if(data->txn) { + res = PQexec(data->conn, "COMMIT;"); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(data->conn) != CONNECTION_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(data->conn); + res = PQexec(data->conn, "COMMIT;"); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: sql transaction commit failed: %s", PQresultErrorMessage(res)); + PQclear(res); + PQclear(PQexec(data->conn, "ROLLBACK;")); + return st_FAILED; + } + PQclear(res); + } + + return st_SUCCESS; +} + +static st_ret_t _st_pgsql_get(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t *os) { + drvdata_t data = (drvdata_t) drv->private; + char *cond, *buf = NULL; + int buflen = 0; + PGresult *res; + int ntuples, nfields, i, j; + os_object_t o; + char *fname, *val; + os_type_t ot; + int ival; + char tbuf[128]; + + if(data->prefix != NULL) { + snprintf(tbuf, sizeof(tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + cond = _st_pgsql_convert_filter(drv, owner, filter); + log_debug(ZONE, "generated filter: %s", cond); + + PGSQL_SAFE(buf, strlen(type) + strlen(cond) + 51, buflen); + sprintf(buf, "SELECT * FROM \"%s\" WHERE %s ORDER BY \"object-sequence\";", type, cond); + free(cond); + + log_debug(ZONE, "prepared sql: %s", buf); + + res = PQexec(data->conn, buf); + + if(PQresultStatus(res) != PGRES_TUPLES_OK && PQstatus(data->conn) != CONNECTION_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(data->conn); + res = PQexec(data->conn, buf); + } + + free(buf); + + if(PQresultStatus(res) != PGRES_TUPLES_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: sql select failed: %s", PQresultErrorMessage(res)); + PQclear(res); + return st_FAILED; + } + + ntuples = PQntuples(res); + if(ntuples == 0) { + PQclear(res); + return st_NOTFOUND; + } + + log_debug(ZONE, "%d tuples returned", ntuples); + + nfields = PQnfields(res); + + if(nfields == 0) { + log_debug(ZONE, "weird, tuples were returned but no fields *shrug*"); + PQclear(res); + return st_NOTFOUND; + } + + *os = os_new(); + + for(i = 0; i < ntuples; i++) { + o = os_object_new(*os); + + for(j = 0; j < nfields; j++) { + fname = PQfname(res, j); + if(strcmp(fname, "collection-owner") == 0 || strcmp(fname, "object-sequence") == 0) + continue; + + switch(PQftype(res, j)) { + case 16: /* boolean */ + ot = os_type_BOOLEAN; + break; + + case 23: /* integer */ + ot = os_type_INTEGER; + break; + + case 25: /* text */ + ot = os_type_STRING; + break; + + default: + log_debug(ZONE, "unknown oid %d, ignoring it", PQfname(res, j)); + continue; + } + + if(PQgetisnull(res, i, j)) + continue; + + val = PQgetvalue(res, i, j); + + switch(ot) { + case os_type_BOOLEAN: + ival = (val[0] == 't') ? 1 : 0; + os_object_put(o, fname, &ival, ot); + break; + + case os_type_INTEGER: + ival = atoi(val); + os_object_put(o, fname, &ival, ot); + break; + + case os_type_STRING: + os_object_put(o, fname, val, os_type_STRING); + break; + + case os_type_NAD: + case os_type_UNKNOWN: + break; + } + } + } + + PQclear(res); + + return st_SUCCESS; +} + +static st_ret_t _st_pgsql_delete(st_driver_t drv, const char *type, const char *owner, const char *filter) { + drvdata_t data = (drvdata_t) drv->private; + char *cond, *buf = NULL; + int buflen = 0; + PGresult *res; + char tbuf[128]; + + if(data->prefix != NULL) { + snprintf(tbuf, sizeof(tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + cond = _st_pgsql_convert_filter(drv, owner, filter); + log_debug(ZONE, "generated filter: %s", cond); + + PGSQL_SAFE(buf, strlen(type) + strlen(cond) + 23, buflen); + sprintf(buf, "DELETE FROM \"%s\" WHERE %s;", type, cond); + free(cond); + + log_debug(ZONE, "prepared sql: %s", buf); + + res = PQexec(data->conn, buf); + + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(data->conn) != CONNECTION_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(data->conn); + res = PQexec(data->conn, buf); + } + + free(buf); + + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: sql delete failed: %s", PQresultErrorMessage(res)); + PQclear(res); + return st_FAILED; + } + + PQclear(res); + + return st_SUCCESS; +} + +static st_ret_t _st_pgsql_replace(st_driver_t drv, const char *type, const char *owner, const char *filter, os_t os) { + drvdata_t data = (drvdata_t) drv->private; + PGresult *res; + + if(data->txn) { + res = PQexec(data->conn, "BEGIN;"); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(data->conn) != CONNECTION_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(data->conn); + res = PQexec(data->conn, "BEGIN;"); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: sql transaction begin failed: %s", PQresultErrorMessage(res)); + PQclear(res); + return st_FAILED; + } + PQclear(res); + + res = PQexec(data->conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;"); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(data->conn) != CONNECTION_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(data->conn); + res = PQexec(data->conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;"); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: sql transaction setup failed: %s", PQresultErrorMessage(res)); + PQclear(res); + PQclear(PQexec(data->conn, "ROLLBACK;")); + return st_FAILED; + } + PQclear(res); + } + + if(_st_pgsql_delete(drv, type, owner, filter) == st_FAILED) { + if(data->txn) + PQclear(PQexec(data->conn, "ROLLBACK;")); + return st_FAILED; + } + + if(_st_pgsql_put_guts(drv, type, owner, os) == st_FAILED) { + if(data->txn) + PQclear(PQexec(data->conn, "ROLLBACK;")); + return st_FAILED; + } + + if(data->txn) { + res = PQexec(data->conn, "COMMIT;"); + if(PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(data->conn) != CONNECTION_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: lost connection to database, attempting reconnect"); + PQclear(res); + PQreset(data->conn); + res = PQexec(data->conn, "COMMIT;"); + } + if(PQresultStatus(res) != PGRES_COMMAND_OK) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: sql transaction commit failed: %s", PQresultErrorMessage(res)); + PQclear(res); + PQclear(PQexec(data->conn, "ROLLBACK;")); + return st_FAILED; + } + PQclear(res); + } + + return st_SUCCESS; +} + +static void _st_pgsql_free(st_driver_t drv) { + drvdata_t data = (drvdata_t) drv->private; + + PQfinish(data->conn); + + xhash_free(data->filters); + + free(data); +} + +st_ret_t st_pgsql_init(st_driver_t drv) { + char *host, *port, *dbname, *user, *pass; + PGconn *conn; + drvdata_t data; + + host = config_get_one(drv->st->sm->config, "storage.pgsql.host", 0); + port = config_get_one(drv->st->sm->config, "storage.pgsql.port", 0); + dbname = config_get_one(drv->st->sm->config, "storage.pgsql.dbname", 0); + user = config_get_one(drv->st->sm->config, "storage.pgsql.user", 0); + pass = config_get_one(drv->st->sm->config, "storage.pgsql.pass", 0); + + if(host == NULL || port == NULL || dbname == NULL || user == NULL || pass == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: invalid driver config"); + return st_FAILED; + } + + conn = PQsetdbLogin(host, port, NULL, NULL, dbname, user, pass); + if(conn == NULL) { + log_write(drv->st->sm->log, LOG_ERR, "pgsql: unable to allocate database connection state"); + return st_FAILED; + } + + if(PQstatus(conn) != CONNECTION_OK) + log_write(drv->st->sm->log, LOG_ERR, "pgsql: connection to database failed: %s", PQerrorMessage(conn)); + + data = (drvdata_t) malloc(sizeof(struct drvdata_st)); + memset(data, 0, sizeof(struct drvdata_st)); + + data->conn = conn; + + data->filters = xhash_new(17); + + if(config_get_one(drv->st->sm->config, "storage.pgsql.transactions", 0) != NULL) + data->txn = 1; + else + log_write(drv->st->sm->log, LOG_WARNING, "pgsql: transactions disabled"); + + data->prefix = config_get_one(drv->st->sm->config, "storage.pgsql.prefix", 0); + + drv->private = (void *) data; + + drv->add_type = _st_pgsql_add_type; + drv->put = _st_pgsql_put; + drv->get = _st_pgsql_get; + drv->delete = _st_pgsql_delete; + drv->replace = _st_pgsql_replace; + drv->free = _st_pgsql_free; + + return st_SUCCESS; +} + +#endif diff --git a/sm/storage_sqlite.c b/sm/storage_sqlite.c new file mode 100644 index 00000000..9307ad24 --- /dev/null +++ b/sm/storage_sqlite.c @@ -0,0 +1,671 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * Copyright (c) 2004 Christof Meerwald + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* Released under the GPL by Chris Parker , IVSN + * to the Jabberd project. + */ + +/* Modified and updated for SQLite 3 by Christof Meerwald, + * http://cmeerw.org + */ + +#include "sm.h" + +#ifdef STORAGE_SQLITE + +#include + +/** internal structure, holds our data */ +typedef struct drvdata_st { + sqlite3 *db; + char *prefix; + int txn; +} *drvdata_t; + +#define BLOCKSIZE (1024) + + +/** internal: do and return the math and ensure it gets realloc'd */ +static int _st_sqlite_realloc (void **oblocks, int len) { + void *nblocks; + int nlen; + + /* round up to standard block sizes */ + nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE; + + /* keep trying till we get it */ + while ((nblocks = realloc(*oblocks, nlen)) == NULL) sleep (1); + *oblocks = nblocks; + + return nlen; +} + +/** this is the safety check used to make sure there's always enough mem */ +#define SQLITE_SAFE(blocks, size, len) \ + if((size) >= (len)) \ + len = _st_sqlite_realloc((void**)&(blocks),(size) + 1); + +#define SQLITE_SAFE_CAT(blocks, size, len, s1) \ + do { \ + SQLITE_SAFE(blocks, size + sizeof (s1) - 1, len); \ + memcpy (&blocks[size], s1, sizeof (s1)); \ + size += sizeof (s1) - 1; \ + } while (0) + +#define SQLITE_SAFE_CAT3(blocks, size, len, s1, s2, s3) \ + do { \ + const unsigned int l = strlen (s2); \ + SQLITE_SAFE(blocks, size + sizeof (s1) + l + sizeof (s2) - 2, len); \ + memcpy (&blocks[size], s1, sizeof (s1) - 1); \ + memcpy (&blocks[size + sizeof (s1) - 1], s2, l); \ + memcpy (&blocks[size + sizeof (s1) - 1 + l], s3, sizeof (s3)); \ + size += sizeof (s1) + l + sizeof (s3) - 2; \ + } while (0) + +static void _st_sqlite_convert_filter_recursive (st_filter_t f, char **buf, + int *buflen, int *nbuf) { + + st_filter_t scan; + + switch (f->type) { + case st_filter_type_PAIR: + SQLITE_SAFE_CAT3 ((*buf), *nbuf, *buflen, + "( \"", f->key, "\" = ? ) "); + break; + + case st_filter_type_AND: + SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "( "); + + for (scan = f->sub; scan != NULL; scan = scan->next) { + _st_sqlite_convert_filter_recursive (scan, buf, + buflen, nbuf); + + if (scan->next != NULL) { + SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "AND "); + } + } + + SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, ") "); + + return; + + case st_filter_type_OR: + SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "( "); + + for (scan = f->sub; scan != NULL; scan = scan->next) { + _st_sqlite_convert_filter_recursive (scan, buf, + buflen, nbuf); + + if (scan->next != NULL) { + SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "OR "); + } + } + + SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, ") "); + + return; + + case st_filter_type_NOT: + SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, "( NOT "); + + _st_sqlite_convert_filter_recursive(f->sub, buf, + buflen, nbuf); + + SQLITE_SAFE_CAT ((*buf), *nbuf, *buflen, ") "); + + return; + } +} + +static char *_st_sqlite_convert_filter (st_driver_t drv, const char *owner, + const char *filter) { + + drvdata_t data = (drvdata_t) drv->private; + char *buf = NULL; + unsigned int buflen = 0, nbuf = 0; + st_filter_t f; + + + SQLITE_SAFE_CAT (buf, nbuf, buflen, "\"collection-owner\" = ?"); + + f = storage_filter (filter); + if (f == NULL) { + return buf; + } + + SQLITE_SAFE_CAT (buf, nbuf, buflen, " AND "); + + _st_sqlite_convert_filter_recursive (f, &buf, &buflen, &nbuf); + + pool_free (f->p); + + return buf; +} + +static void _st_sqlite_bind_filter_recursive (st_filter_t f, + sqlite3_stmt *stmt, + unsigned int bind_off) { + + st_filter_t scan; + unsigned int i; + + switch (f->type) { + case st_filter_type_PAIR: + sqlite3_bind_text (stmt, bind_off, f->val, strlen (f->val), + SQLITE_TRANSIENT); + break; + + case st_filter_type_AND: + for (scan = f->sub, i = 0; scan != NULL; scan = scan->next, ++i) { + _st_sqlite_bind_filter_recursive (scan, stmt, bind_off + i); + } + return; + + case st_filter_type_OR: + for (scan = f->sub, i = 0; scan != NULL; scan = scan->next, ++i) { + _st_sqlite_bind_filter_recursive (scan, stmt, bind_off + i); + } + return; + + case st_filter_type_NOT: + _st_sqlite_bind_filter_recursive(f->sub, stmt, bind_off); + return; + } +} + +static void _st_sqlite_bind_filter (st_driver_t drv, const char *owner, + const char *filter, + sqlite3_stmt *stmt, + unsigned int bind_off) { + + drvdata_t data = (drvdata_t) drv->private; + st_filter_t f; + + + sqlite3_bind_text (stmt, bind_off, owner, strlen (owner), + SQLITE_TRANSIENT); + + f = storage_filter (filter); + if (f == NULL) { + return; + } + + _st_sqlite_bind_filter_recursive (f, stmt, bind_off + 1); + + pool_free (f->p); +} + +static st_ret_t _st_sqlite_add_type (st_driver_t drv, const char *type) { + + return st_SUCCESS; +} + +static st_ret_t _st_sqlite_put_guts (st_driver_t drv, const char *type, + const char *owner, os_t os) { + + drvdata_t data = (drvdata_t) drv->private; + char *left = NULL, *right = NULL; + unsigned int lleft = 0, lright = 0; + os_object_t o; + char *key, *cval = NULL; + void *val; + os_type_t ot; + char *xml; + int xlen; + char tbuf[128]; + int res; + + if (os_count (os) == 0) { + return st_SUCCESS; + } + + if (data->prefix != NULL) { + snprintf (tbuf, sizeof (tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + if (os_iter_first (os)) { + do { + + unsigned int i = 0; + unsigned int nleft = 0, nright = 0; + sqlite3_stmt *stmt; + + + SQLITE_SAFE_CAT3 (left, nleft, lleft, + "INSERT INTO \"", type, + "\" ( \"collection-owner\""); + SQLITE_SAFE_CAT (right, nright, lright, " ) VALUES ( ?"); + + o = os_iter_object (os); + if (os_object_iter_first(o)) + do { + os_object_iter_get (o, &key, &val, &ot); + + log_debug (ZONE, "key %s val %s", key, cval); + + SQLITE_SAFE_CAT3 (left, nleft, lleft, + ", \"", key, "\""); + + SQLITE_SAFE_CAT (right, nright, lright, ", ?"); + + } while (os_object_iter_next (o)); + + SQLITE_SAFE (left, nleft + nright, lleft); + memcpy (&left[nleft], right, nright); + nleft += nright; + free (right); + + SQLITE_SAFE_CAT (left, nleft, lleft, " )"); + + log_debug (ZONE, "prepared sql: %s", left); + + res = sqlite3_prepare (data->db, left, strlen (left), &stmt, NULL); + free (left); + if (res != SQLITE_OK) { + log_write (drv->st->sm->log, LOG_ERR, + "sqlite: sql insert failed: %s", + sqlite3_errmsg (data->db)); + return st_FAILED; + } + + sqlite3_bind_text (stmt, 1, owner, strlen (owner), + SQLITE_TRANSIENT); + + o = os_iter_object (os); + if (os_object_iter_first(o)) + do { + os_object_iter_get (o, &key, &val, &ot); + + switch(ot) { + case os_type_BOOLEAN: + sqlite3_bind_int (stmt, i + 2, (int) val ? 1 : 0); + break; + + case os_type_INTEGER: + sqlite3_bind_int (stmt, i + 2, (int) val); + break; + + case os_type_STRING: + sqlite3_bind_text (stmt, i + 2, + (const char *) val, + strlen ((const char *) val), + SQLITE_TRANSIENT); + break; + + /* !!! might not be a good idea to mark nads this way */ + case os_type_NAD: + nad_print ((nad_t) val, 0, &xml, &xlen); + cval = (char *) malloc(sizeof(char) * (xlen + 4)); + memcpy (&cval[3], xml, xlen + 1); + memcpy (cval, "NAD", 3); + + sqlite3_bind_text (stmt, i + 2, + cval, xlen + 3, free); + break; + } + + i += 1; + } while (os_object_iter_next (o)); + + res = sqlite3_step (stmt); + if (res != SQLITE_DONE) { + log_write (drv->st->sm->log, LOG_ERR, + "sqlite: sql insert failed: %s", + sqlite3_errmsg (data->db)); + sqlite3_finalize (stmt); + return st_FAILED; + } + sqlite3_finalize (stmt); + + } while (os_iter_next (os)); + } + + return st_SUCCESS; +} + +static st_ret_t _st_sqlite_put (st_driver_t drv, const char *type, + const char *owner, os_t os) { + + drvdata_t data = (drvdata_t) drv->private; + int res; + char *err_msg = NULL; + + if (os_count (os) == 0) { + return st_SUCCESS; + } + + if (data->txn) { + + res = sqlite3_exec (data->db, + "BEGIN", NULL, NULL, + &err_msg); + if (res != SQLITE_OK) { + log_write (drv->st->sm->log, LOG_ERR, + "sqlite: sql transaction begin failed: %s", + err_msg); + sqlite3_free (err_msg); + return st_FAILED; + } + } + + if (_st_sqlite_put_guts (drv, type, owner, os) != st_SUCCESS) { + if (data->txn) { + res = sqlite3_exec (data->db, "ROLLBACK", + NULL, NULL, NULL); + } + return st_FAILED; + } + + if (data->txn) { + + res = sqlite3_exec (data->db, "COMMIT", NULL, NULL, &err_msg); + if (res != SQLITE_OK) { + log_write (drv->st->sm->log, LOG_ERR, + "sqlite: sql transaction commit failed: %s", + err_msg); + sqlite3_exec (data->db, "ROLLBACK", NULL, NULL, NULL); + return st_FAILED; + } + } + return st_SUCCESS; +} + +static st_ret_t _st_sqlite_get (st_driver_t drv, const char *type, + const char *owner, const char *filter, + os_t *os) { + + drvdata_t data = (drvdata_t) drv->private; + char *cond, *buf = NULL; + unsigned int nbuf = 0; + unsigned int buflen = 0; + int i; + unsigned long *lengths; + unsigned int num_rows = 0; + os_object_t o; + const char *val; + os_type_t ot; + int ival; + nad_t nad; + char tbuf[128]; + + sqlite3_stmt *stmt; + int result; + + if (data->prefix != NULL) { + snprintf (tbuf, sizeof (tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + cond = _st_sqlite_convert_filter (drv, owner, filter); + + SQLITE_SAFE_CAT3 (buf, nbuf, buflen, + "SELECT * FROM \"", type, "\" WHERE "); + strcpy (&buf[nbuf], cond); + /* ORDER BY 'object-sequence'", type, cond); */ + free (cond); + + log_debug (ZONE, "prepared sql: %s", buf); + + result = sqlite3_prepare (data->db, buf, strlen (buf), &stmt, NULL); + free (buf); + if (result != SQLITE_OK) { + return st_FAILED; + } + + _st_sqlite_bind_filter (drv, owner, filter, stmt, 1); + + *os = os_new (); + + do { + + unsigned int num_cols; + + result = sqlite3_step (stmt); + + if (result != SQLITE_ROW) { + continue; + } + + o = os_object_new (*os); + num_cols = sqlite3_data_count (stmt); + + for (i = 0; i < num_cols; i++) { + + const char *colname; + int coltype; + + colname = sqlite3_column_name (stmt, i); + + if (strcmp (colname, "collection-owner") == 0 || + strcmp (colname, "object-sequence") == 0) { + continue; + } + + coltype = sqlite3_column_type (stmt, i); + + if (coltype == SQLITE_NULL) { + log_debug (ZONE, "coldata is NULL"); + continue; + } + + if (coltype == SQLITE_INTEGER) { + if (!strcmp (sqlite3_column_decltype (stmt, i), "BOOL")) { + ot = os_type_BOOLEAN; + } else { + ot = os_type_INTEGER; + } + + ival = sqlite3_column_int (stmt, i); + os_object_put (o, colname, &ival, ot); + + } else if (coltype == SQLITE3_TEXT) { + ot = os_type_STRING; + + val = sqlite3_column_text (stmt, i); + os_object_put (o, colname, val, ot); + + } else { + log_write (drv->st->sm->log, + LOG_NOTICE, + "sqlite: unknown field: %s:%d", + colname, coltype); + } + } + + num_rows++; + + } while (result == SQLITE_ROW); + + sqlite3_finalize (stmt); + + if (num_rows == 0) { + return st_NOTFOUND; + } + + return st_SUCCESS; +} + +static st_ret_t _st_sqlite_delete (st_driver_t drv, const char *type, + const char *owner, const char *filter) { + + drvdata_t data = (drvdata_t) drv->private; + char *cond, *buf = NULL; + unsigned int nbuf = 0; + unsigned int buflen = 0; + char tbuf[128]; + int res; + sqlite3_stmt *stmt; + + if (data->prefix != NULL) { + snprintf (tbuf, sizeof (tbuf), "%s%s", data->prefix, type); + type = tbuf; + } + + cond = _st_sqlite_convert_filter (drv, owner, filter); + log_debug (ZONE, "generated filter: %s", cond); + + SQLITE_SAFE_CAT3 (buf, nbuf, buflen, + "DELETE FROM \"", type, "\" WHERE "); + strcpy (&buf[nbuf], cond); + free (cond); + + log_debug (ZONE, "prepared sql: %s", buf); + + res = sqlite3_prepare (data->db, buf, strlen (buf), &stmt, NULL); + free (buf); + if (res != SQLITE_OK) { + return st_FAILED; + } + + _st_sqlite_bind_filter (drv, owner, filter, stmt, 1); + + res = sqlite3_step (stmt); + if (res != SQLITE_DONE) { + log_write (drv->st->sm->log, LOG_ERR, + "sqlite: sql delete failed: %s", + sqlite3_errmsg (data->db)); + sqlite3_finalize (stmt); + return st_FAILED; + } + sqlite3_finalize (stmt); + + return st_SUCCESS; +} + +static st_ret_t _st_sqlite_replace (st_driver_t drv, const char *type, + const char *owner, const char *filter, + os_t os) { + + drvdata_t data = (drvdata_t) drv->private; + + int res; + char *err_msg = NULL; + + if (data->txn) { + + res = sqlite3_exec (data->db, "BEGIN", NULL, NULL, &err_msg); + if (res != SQLITE_OK) { + log_write (drv->st->sm->log, LOG_ERR, + "sqlite: sql transaction begin failed: %s", + err_msg); + sqlite3_free (err_msg); + return st_FAILED; + } + } + + if (_st_sqlite_delete (drv, type, owner, filter) == st_FAILED) { + if (data->txn) { + sqlite3_exec (data->db, "ROLLBACK", NULL, NULL, NULL); + } + return st_FAILED; + } + + if (_st_sqlite_put_guts (drv, type, owner, os) == st_FAILED) { + if (data->txn) { + sqlite3_exec (data->db, "ROLLBACK", NULL, NULL, NULL); + } + return st_FAILED; + } + + if (data->txn) { + + res = sqlite3_exec (data->db, "COMMIT", NULL, NULL, &err_msg); + + if (res != SQLITE_OK) { + log_write (drv->st->sm->log, LOG_ERR, + "sqlite: sql transaction commit failed: %s", + err_msg); + sqlite3_exec (data->db, "ROLLBACK", NULL, NULL, NULL); + + return st_FAILED; + } + } + + return st_SUCCESS; +} + +static void _st_sqlite_free (st_driver_t drv) { + + drvdata_t data = (drvdata_t) drv->private; + + sqlite3_close (data->db); + + free (data); +} + +st_ret_t st_sqlite_init(st_driver_t drv) { + + char *dbname; + sqlite3 *db; + drvdata_t data; + int ret; + char *busy_timeout; + + dbname = config_get_one (drv->st->sm->config, + "storage.sqlite.dbname", 0); + if (dbname == NULL) { + log_write (drv->st->sm->log, LOG_ERR, + "sqlite: invalid driver config"); + return st_FAILED; + } + + ret = sqlite3_open (dbname, &db); + if (ret != SQLITE_OK) { + log_write (drv->st->sm->log, LOG_ERR, + "sqlite: can't open database"); + return st_FAILED; + } + + data = (drvdata_t) malloc (sizeof (struct drvdata_st)); + memset(data, 0, sizeof(struct drvdata_st)); + + data->db = db; + + if (config_get_one (drv->st->sm->config, + "storage.sqlite.transactions", 0) != NULL) { + data->txn = 1; + } else { + log_write (drv->st->sm->log, LOG_WARNING, + "sqlite: transactions disabled"); + } + + busy_timeout = config_get_one (drv->st->sm->config, + "storage.sqlite.busy-timeout", 0); + if (busy_timeout != NULL) { + sqlite3_busy_timeout (db, atoi (busy_timeout)); + } + + data->prefix = config_get_one (drv->st->sm->config, + "storage.sqlite.prefix", 0); + + drv->private = (void *) data; + drv->add_type = _st_sqlite_add_type; + drv->put = _st_sqlite_put; + drv->get = _st_sqlite_get; + drv->delete = _st_sqlite_delete; + drv->replace = _st_sqlite_replace; + drv->free = _st_sqlite_free; + + return st_SUCCESS; +} + +#endif diff --git a/sm/user.c b/sm/user.c new file mode 100644 index 00000000..559273ce --- /dev/null +++ b/sm/user.c @@ -0,0 +1,138 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sm.h" + +/** @file sm/user.c + * @brief user management + * @author Robert Norris + * $Date: 2005/06/02 04:48:25 $ + * $Revision: 1.23 $ + */ + +/** make a new one */ +static user_t _user_alloc(sm_t sm, jid_t jid) { + pool p; + user_t user; + + p = pool_new(); + + user = (user_t) pmalloco(p, sizeof(struct user_st)); + + user->p = p; + user->sm = sm; + + user->jid = jid_dup(jid); + pool_cleanup(p, (void (*)(void *)) jid_free, user->jid); + + /* a place for modules to store stuff */ + user->module_data = (void **) pmalloco(p, sizeof(void *) * sm->mm->nindex); + + return user; +} + +/** fetch user data */ +user_t user_load(sm_t sm, jid_t jid) { + user_t user; + + /* already loaded */ + user = xhash_get(sm->users, jid_user(jid)); + if(user != NULL) { + log_debug(ZONE, "returning previously-created user data for %s", jid_user(jid)); + return user; + } + + /* make a new one */ + user = _user_alloc(sm, jid); + + /* get modules to setup */ + if(mm_user_load(sm->mm, user) != 0) { + log_debug(ZONE, "modules failed user load for %s", jid_user(jid)); + pool_free(user->p); + return NULL; + } + + /* save them for later */ + xhash_put(sm->users, jid_user(user->jid), (void *) user); + + log_debug(ZONE, "loaded user data for %s", jid_user(jid)); + + return user; +} + +void user_free(user_t user) { + log_debug(ZONE, "freeing user %s", jid_user(user->jid)); + + xhash_zap(user->sm->users, jid_user(user->jid)); + pool_free(user->p); +} + +/** initialise a user */ +int user_create(sm_t sm, jid_t jid) { + user_t user; + + log_debug(ZONE, "create user request for %s", jid_user(jid)); + + user = user_load(sm, jid); + if(user != NULL) { + log_write(sm->log, LOG_ERR, "request to create already-active user: jid=%s", jid_user(jid)); + log_debug(ZONE, "user already active, not creating"); + return 1; + } + + /* modules create */ + if(mm_user_create(sm->mm, jid) != 0) { + log_write(sm->log, LOG_ERR, "user creation failed: jid=%s", jid_user(jid)); + log_debug(ZONE, "user create failed, forcing deletion for cleanup"); + mm_user_delete(sm->mm, jid); + return 1; + } + + log_write(sm->log, LOG_NOTICE, "created user: jid=%s", jid_user(jid)); + + return 0; +} + +/** trash a user */ +void user_delete(sm_t sm, jid_t jid) { + user_t user; + sess_t scan, next; + + log_debug(ZONE, "delete user request for %s", jid_user(jid)); + + user = user_load(sm, jid); + if(user == NULL) { + log_debug(ZONE, "user doesn't exist, can't delete"); + return; + } + + /* close their sessions first (this will free user, after the last session ends) */ + scan = user->sessions; + while(scan != NULL) { + next = scan->next; + sm_c2s_action(scan, "ended", NULL); + sess_end(scan); + scan = next; + } + + mm_user_delete(sm->mm, jid); + + log_write(sm->log, LOG_NOTICE, "deleted user: jid=%s", jid_user(jid)); +} diff --git a/storage/Makefile.am b/storage/Makefile.am new file mode 100644 index 00000000..a65db9bd --- /dev/null +++ b/storage/Makefile.am @@ -0,0 +1,13 @@ +INCLUDES = -DCONFIG_DIR=\"$(sysconfdir)\" + +bin_PROGRAMS = storage + +storage_SOURCES = main.c +noinst_HEADERS = storage.h + +storage_LDADD = $(top_builddir)/sx/libsx.la \ + $(top_builddir)/scod/libscod.la \ + $(top_builddir)/mio/libmio.la \ + $(top_builddir)/util/libutil.la \ + $(top_builddir)/subst/libsubst.la \ + $(top_builddir)/expat/libexpat.la diff --git a/storage/main.c b/storage/main.c new file mode 100644 index 00000000..ce924f0b --- /dev/null +++ b/storage/main.c @@ -0,0 +1,524 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "storage.h" + +static int storage_shutdown = 0; +static int storage_lost_router = 0; +static int storage_logrotate = 0; + +static void _storage_signal(int signum) { + storage_shutdown = 1; + storage_lost_router = 0; +} + +static void _storage_signal_hup(int signum) { + storage_logrotate = 1; +} + +/** store the process id */ +static void _storage_pidfile(storage_t st) { + char *pidfile; + FILE *f; + pid_t pid; + + pidfile = config_get_one(st->config, "pidfile", 0); + if(pidfile == NULL) + return; + + pid = getpid(); + + if((f = fopen(pidfile, "w+")) == NULL) { + log_write(st->log, LOG_ERR, "couldn't open %s for writing: %s", pidfile, strerror(errno)); + return; + } + + if(fprintf(f, "%d", pid) < 0) { + log_write(st->log, LOG_ERR, "couldn't write to %s: %s", pidfile, strerror(errno)); + return; + } + + fclose(f); + + log_write(st->log, LOG_INFO, "process id is %d, written to %s", pid, pidfile); +} + +/** pull values out of the config file */ +static void _storage_config_expand(storage_t st) { + char *str; + + st->id = config_get_one(st->config, "id", 0); + if(st->id == NULL) + st->id = "storage"; + + st->router_ip = config_get_one(st->config, "router.ip", 0); + if(st->router_ip == NULL) + st->router_ip = "127.0.0.1"; + + st->router_port = j_atoi(config_get_one(st->config, "router.port", 0), 5347); + + st->router_user = config_get_one(st->config, "router.user", 0); + if(st->router_user == NULL) + st->router_user = "jabberd"; + st->router_pass = config_get_one(st->config, "router.pass", 0); + if(st->router_pass == NULL) + st->router_pass = "secret"; + + st->router_pemfile = config_get_one(st->config, "router.pemfile", 0); + + st->retry_init = j_atoi(config_get_one(st->config, "router.retry.init", 0), 3); + st->retry_lost = j_atoi(config_get_one(st->config, "router.retry.lost", 0), 3); + if((st->retry_sleep = j_atoi(config_get_one(st->config, "router.retry.sleep", 0), 2)) < 1) + st->retry_sleep = 1; + + st->log_type = log_STDOUT; + if(config_get(st->config, "log") != NULL) { + if((str = config_get_attr(st->config, "log", 0, "type")) != NULL) { + if(strcmp(str, "file") == 0) + st->log_type = log_FILE; + else if(strcmp(str, "syslog") == 0) + st->log_type = log_SYSLOG; + } + } + + if(st->log_type == log_SYSLOG) { + st->log_facility = config_get_one(st->config, "log.facility", 0); + st->log_ident = config_get_one(st->config, "log.ident", 0); + if(st->log_ident == NULL) + st->log_ident = "jabberd/storage"; + } else if(st->log_type == log_FILE) + st->log_ident = config_get_one(st->config, "log.file", 0); +} + +static int _storage_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) { + storage_t st = (storage_t) arg; + sx_buf_t buf = (sx_buf_t) data; + sx_error_t *sxe; + int elem, len, ns, attr; + nad_t nad; + + switch(e) { + case event_WANT_READ: + log_debug("want read"); + mio_read(st->mio, st->fd); + break; + + case event_WANT_WRITE: + log_debug("want write"); + mio_write(st->mio, st->fd); + break; + + case event_READ: + log_debug("reading from %d", st->fd); + + /* do the read */ + len = recv(st->fd, buf->data, buf->len, 0); + + if(len < 0) { + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) { + buf->len = 0; + return 0; + } + + log_write(st->log, LOG_NOTICE, "[%d] [router] read error: %s (%d)", st->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + } + + else if(len == 0) { + /* they went away */ + sx_kill(s); + + return -1; + } + + log_debug("read %d bytes", len); + + buf->len = len; + + return len; + + case event_WRITE: + log_debug("writing to %d", st->fd); + + len = send(st->fd, buf->data, buf->len, 0); + if(len >= 0) { + log_debug("%d bytes written", len); + return len; + } + + if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) + return 0; + + log_write(st->log, LOG_NOTICE, "[%d] [router] write error: %s (%d)", st->fd, strerror(errno), errno); + + sx_kill(s); + + return -1; + + case event_ERROR: + sxe = (sx_error_t *) data; + log_write(st->log, LOG_NOTICE, "error from router: %s (%s)", sxe->generic, sxe->specific); + + if(sxe->code == SX_ERR_AUTH) + sx_close(s); + + break; + + case event_STREAM: + break; + + case event_OPEN: + log_write(st->log, LOG_NOTICE, "connection to router established"); + + nad = nad_new(st->router->nad_cache); + ns = nad_add_namespace(nad, uri_COMPONENT, NULL); + nad_append_elem(nad, ns, "bind", 0); + nad_append_attr(nad, -1, "name", st->id); + + log_debug("requesting component bind for '%s'", st->id); + + sx_nad_write(st->router, nad); + + break; + + case event_PACKET: + nad = (nad_t) data; + + /* drop unqualified packets */ + if(NAD_ENS(nad, 0) < 0) { + nad_free(nad); + return 0; + } + + /* watch for the features packet */ + if(s->state == state_STREAM) { + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_STREAMS) || strncmp(uri_STREAMS, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_STREAMS)) != 0 || NAD_ENAME_L(nad, 0) != 8 || strncmp("features", NAD_ENAME(nad, 0), 8)) { + log_debug("got a non-features packet on an unauth'd stream, dropping"); + nad_free(nad); + return 0; + } + +#ifdef HAVE_SSL + /* starttls if we can */ + if(st->sx_ssl != NULL && s->ssf == 0) { + ns = nad_find_scoped_namespace(nad, uri_TLS, NULL); + if(ns >= 0) { + elem = nad_find_elem(nad, 0, ns, "starttls", 1); + if(elem >= 0) { + if(sx_ssl_client_starttls(st->sx_ssl, s, NULL) == 0) { + nad_free(nad); + return 0; + } + log_write(st->log, LOG_NOTICE, "unable to establish encrypted session with router"); + } + } + } +#endif + + /* !!! pull the list of mechanirs, and choose the best one. + * if there isn't an appropriate one, error and bail */ + + /* authenticate */ + sx_sasl_auth(st->sx_sasl, s, "DIGEST-MD5", st->router_user, st->router_pass, NULL); + + nad_free(nad); + return 0; + } + + /* watch for the bind response */ + if(s->state == state_OPEN && !st->online) { + if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_COMPONENT) || strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) != 0 || NAD_ENAME_L(nad, 0) != 4 || strncmp("bind", NAD_ENAME(nad, 0), 4)) { + log_debug("got a packet from router, but we're not online, dropping"); + nad_free(nad); + return 0; + } + + /* catch errors */ + attr = nad_find_attr(nad, 0, -1, "error", NULL); + if(attr >= 0) { + log_write(st->log, LOG_NOTICE, "router refused bind request (%.*s)", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + exit(1); + } + + log_debug("coming online"); + + /* we're online */ + st->online = st->started = 1; + st->retry_left = st->retry_lost; + + log_write(st->log, LOG_NOTICE, "ready for requests", st->id); + + nad_free(nad); + return 0; + } + + break; + + case event_CLOSED: + mio_close(st->mio, st->fd); + break; + } + + return 0; +} + +static int _storage_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) { + storage_t st = (storage_t) arg; + int nbytes; + + switch(a) { + case action_READ: + + ioctl(fd, FIONREAD, &nbytes); + if(nbytes == 0) { + sx_kill(st->router); + return 0; + } + + log_debug("read action on fd %d", fd); + return sx_can_read(st->router); + + case action_WRITE: + log_debug("write action on fd %d", fd); + return sx_can_write(st->router); + + case action_CLOSE: + log_debug("close action on fd %d", fd); + log_write(st->log, LOG_NOTICE, "connection to router closed"); + + storage_lost_router = 1; + + /* we're offline */ + st->online = 0; + + break; + + case action_ACCEPT: + break; + } + + return 0; +} + +static int _storage_router_connect(storage_t st) { + log_write(st->log, LOG_NOTICE, "attempting connection to router at %s, port=%d", st->router_ip, st->router_port); + + st->fd = mio_connect(st->mio, st->router_port, st->router_ip, _storage_mio_callback, (void *) st); + if(st->fd < 0) { + if(errno == ECONNREFUSED) + storage_lost_router = 1; + log_write(st->log, LOG_NOTICE, "connection attempt to router failed: %s (%d)", strerror(errno), errno); + return 1; + } + + st->router = sx_new(st->sx_env, st->fd, _storage_sx_callback, (void *) st); + sx_client_init(st->router, 0, NULL, NULL, NULL, "1.0"); + + return 0; +} + +int main(int argc, char **argv) +{ + storage_t st; + char *config_file; + int optchar; +#ifdef POOL_DEBUG + time_t pool_time = 0; +#endif + +#ifdef HAVE_UMASK + umask((mode_t) 0027); +#endif + + srand(time(NULL)); + +#ifdef HAVE_WINSOCK2_H +/* get winsock running */ + { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + /* !!! tell user that we couldn't find a usable winsock dll */ + return 0; + } + } +#endif + + signal(SIGINT, _storage_signal); + signal(SIGTERM, _storage_signal); +#ifdef SIGHUP + signal(SIGHUP, _storage_signal_hup); +#endif +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + st = (storage_t) malloc(sizeof(struct storage_st)); + memset(st, 0, sizeof(struct storage_st)); + + /* load our config */ + st->config = config_new(); + + config_file = CONFIG_DIR "/storage.xml"; + + /* cmdline parsing */ + while((optchar = getopt(argc, argv, "Dc:h?")) >= 0) + { + switch(optchar) + { + case 'c': + config_file = optarg; + break; + case 'D': +#ifdef DEBUG + set_debug_flag(1); +#else + printf("WARN: Debugging not enabled. Ignoring -D.\n"); +#endif + break; + case 'h': case '?': default: + fputs( + "storage - jabberd storage manager (" VERSION ")\n" + "Usage: storage \n" + "Options are:\n" + " -c config file to use [default: " CONFIG_DIR "/storage.xml]\n" +#ifdef DEBUG + " -D Show debug output\n" +#endif + , + stdout); + config_free(st->config); + free(st); + return 1; + } + } + + if(config_load(st->config, config_file) != 0) + { + fputs("storage: couldn't load config, aborting\n", stderr); + config_free(st->config); + free(st); + return 2; + } + + _storage_config_expand(st); + + st->log = log_new(st->log_type, st->log_ident, st->log_facility); + log_write(st->log, LOG_NOTICE, "starting up"); + + _storage_pidfile(st); + + st->sx_env = sx_env_new(); + +#ifdef HAVE_SSL + if(st->router_pemfile != NULL) { + st->sx_ssl = sx_env_plugin(st->sx_env, sx_ssl_init, st->router_pemfile); + if(st->sx_ssl == NULL) { + log_write(st->log, LOG_ERR, "failed to load SSL pemfile, SSL disabled"); + st->router_pemfile = NULL; + } + } +#endif + + /* get sasl online */ + st->sx_sasl = sx_env_plugin(st->sx_env, sx_sasl_init, NULL, NULL, 0); + if(st->sx_sasl == NULL) { + log_write(st->log, LOG_ERR, "failed to initialise SASL context, aborting"); + exit(1); + } + + st->mio = mio_new(1023); + + st->retry_left = st->retry_init; + _storage_router_connect(st); + + while(!storage_shutdown) { + mio_run(st->mio, 5); + + if(storage_logrotate) { + log_write(st->log, LOG_NOTICE, "reopening log ..."); + log_free(st->log); + st->log = log_new(st->log_type, st->log_ident, st->log_facility); + log_write(st->log, LOG_NOTICE, "log started"); + + storage_logrotate = 0; + } + + if(storage_lost_router) { + if(st->retry_left < 0) { + log_write(st->log, LOG_NOTICE, "attempting reconnect"); + sleep(st->retry_sleep); + storage_lost_router = 0; + _storage_router_connect(st); + } + + else if(st->retry_left == 0) { + storage_shutdown = 1; + } + + else { + log_write(st->log, LOG_NOTICE, "attempting reconnect (%d left)", st->retry_left); + st->retry_left--; + sleep(st->retry_sleep); + storage_lost_router = 0; + _storage_router_connect(st); + } + } + +#ifdef POOL_DEBUG + if(time(NULL) > pool_time + 60) { + pool_stat(1); + pool_time = time(NULL); + } +#endif + } + + log_write(st->log, LOG_NOTICE, "shutting down"); + + sx_free(st->router); + + sx_env_free(st->sx_env); + + mio_free(st->mio); + + log_free(st->log); + + config_free(st->config); + + free(st); + +#ifdef POOL_DEBUG + pool_stat(1); +#endif + +#ifdef HAVE_WINSOCK2_H + WSACleanup(); +#endif + + return 0; +} diff --git a/storage/storage.h b/storage/storage.h new file mode 100644 index 00000000..483fcb6e --- /dev/null +++ b/storage/storage.h @@ -0,0 +1,83 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "mio/mio.h" +#include "sx/sx.h" +#include "sx/ssl.h" +#include "sx/sasl.h" +#include "util/util.h" + +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif + +typedef struct storage_st { + /** our id (hostname) with the router */ + char *id; + + /** how to connect to the router */ + char *router_ip; + int router_port; + char *router_user; + char *router_pass; + char *router_pemfile; + + /** mio context */ + mio_t mio; + + /** sx environment */ + sx_env_t sx_env; + sx_plugin_t sx_ssl; + sx_plugin_t sx_sasl; + + /** router's conn */ + sx_t router; + int fd; + + /** config */ + config_t config; + + /** logging */ + log_t log; + + /** log data */ + log_type_t log_type; + char *log_facility; + char *log_ident; + + /** connect retry */ + int retry_init; + int retry_lost; + int retry_sleep; + int retry_left; + + /** this is true if we've connected to the router at least once */ + int started; + + /** true if we're currently bound in the router */ + int online; +} *storage_t; diff --git a/subst/Makefile.am b/subst/Makefile.am new file mode 100644 index 00000000..1eb296e3 --- /dev/null +++ b/subst/Makefile.am @@ -0,0 +1,6 @@ +noinst_LTLIBRARIES = libsubst.la + +noinst_HEADERS = dirent.h getopt.h ip6_misc.h subst.h syslog.h + +libsubst_la_SOURCES = dirent.c getopt.c gettimeofday.c inet_aton.c inet_ntop.c inet_pton.c snprintf.c syslog.c +libsubst_la_LIBADD = @LDFLAGS@ diff --git a/subst/dirent.c b/subst/dirent.c new file mode 100644 index 00000000..0962bec3 --- /dev/null +++ b/subst/dirent.c @@ -0,0 +1,156 @@ +/* + + Implementation of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003. + Rights: See end of file. + +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if !defined(HAVE_DIRENT_H) && !defined(HAVE_NDIR_H) && !defined(HAVE_SYS_DIR_H) && !defined(HAVE_SYS_NDIR_H) +#ifdef HAVE__FINDFIRST + +#include "dirent.h" + +#include +#include /* _findfirst and _findnext set errno iff they return -1 */ +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct DIR +{ + long handle; /* -1 for failed rewind */ + struct _finddata_t info; + struct dirent result; /* d_name null iff first time */ + char *name; /* null-terminated char string */ +}; + +DIR *opendir(const char *name) +{ + DIR *dir = 0; + + if(name && name[0]) + { + size_t base_length = strlen(name); + const char *all = /* search pattern must end with suitable wildcard */ + strchr("/\\", name[base_length - 1]) ? "*" : "/*"; + + if((dir = (DIR *) malloc(sizeof *dir)) != 0 && + (dir->name = (char *) malloc(base_length + strlen(all) + 1)) != 0) + { + strcat(strcpy(dir->name, name), all); + + if((dir->handle = (long) _findfirst(dir->name, &dir->info)) != -1) + { + dir->result.d_name = 0; + } + else /* rollback */ + { + free(dir->name); + free(dir); + dir = 0; + } + } + else /* rollback */ + { + free(dir); + dir = 0; + errno = ENOMEM; + } + } + else + { + errno = EINVAL; + } + + return dir; +} + +int closedir(DIR *dir) +{ + int result = -1; + + if(dir) + { + if(dir->handle != -1) + { + result = _findclose(dir->handle); + } + + free(dir->name); + free(dir); + } + + if(result == -1) /* map all errors to EBADF */ + { + errno = EBADF; + } + + return result; +} + +struct dirent *readdir(DIR *dir) +{ + struct dirent *result = 0; + + if(dir && dir->handle != -1) + { + if(!dir->result.d_name || _findnext(dir->handle, &dir->info) != -1) + { + result = &dir->result; + result->d_name = dir->info.name; + } + } + else + { + errno = EBADF; + } + + return result; +} + +void rewinddir(DIR *dir) +{ + if(dir && dir->handle != -1) + { + _findclose(dir->handle); + dir->handle = (long) _findfirst(dir->name, &dir->info); + dir->result.d_name = 0; + } + else + { + errno = EBADF; + } +} + +#ifdef __cplusplus +} +#endif + +#endif +#endif + +/* + + Copyright Kevlin Henney, 1997, 2003. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ diff --git a/subst/dirent.h b/subst/dirent.h new file mode 100644 index 00000000..d012742a --- /dev/null +++ b/subst/dirent.h @@ -0,0 +1,62 @@ +#ifndef DIRENT_INCLUDED +#define DIRENT_INCLUDED + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if !defined(HAVE_DIRENT_H) && !defined(HAVE_NDIR_H) && !defined(HAVE_SYS_DIR_H) && !defined(HAVE_SYS_NDIR_H) +#ifdef HAVE__FINDFIRST + +/* + + Declaration of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003. + Rights: See end of file. + +*/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +typedef struct DIR DIR; + +struct dirent +{ + char *d_name; +}; + +DIR *opendir(const char *); +int closedir(DIR *); +struct dirent *readdir(DIR *); +void rewinddir(DIR *); + +/* + + Copyright Kevlin Henney, 1997, 2003. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ + +#ifdef __cplusplus +} +#endif + +#endif +#endif + +#endif diff --git a/subst/getopt.c b/subst/getopt.c new file mode 100644 index 00000000..1bcffb13 --- /dev/null +++ b/subst/getopt.c @@ -0,0 +1,699 @@ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu + before changing it! + + Copyright (C) 1987, 88, 89, 90, 91, 92, 1993 + Free Software Foundation, Inc. + + 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef HAVE_GETOPT + +#ifndef __STDC__ +# ifndef const +# define const +# endif +#endif + +#define __GNU_LIBRARY__ + +/* This tells Alpha OSF/1 not to define a getopt prototype in . */ +#ifndef _NO_PROTO +#define _NO_PROTO +#endif + +#include +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +#include +#endif /* GNU C library. */ + +/* If GETOPT_COMPAT is defined, `+' as well as `--' can introduce a + long-named option. Because this is not POSIX.2 compliant, it is + being phased out. */ +/* #define GETOPT_COMPAT */ + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = 0; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* XXX 1003.2 says this must be 1 before any call. */ +int optind = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +#define BAD_OPTION '\0' +int optopt = BAD_OPTION; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return EOF with `optind' != ARGC. */ + +static enum { + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +#include +#define my_index strchr +#define my_strlen strlen +#else + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +#if __STDC__ || defined(PROTO) +extern char *getenv(const char *name); +extern int strcmp(const char *s1, const char *s2); +extern int strncmp(const char *s1, const char *s2, int n); + +static int my_strlen(const char *s); +static char *my_index(const char *str, int chr); +#else +extern char *getenv(); +#endif + +static int my_strlen(const char *str) +{ + int n = 0; + while (*str++) + n++; + return n; +} + +static char *my_index(const char *str, int chr) +{ + while (*str) { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +#endif /* GNU C library. */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. + + To perform the swap, we first reverse the order of all elements. So + all options now come before all non options, but they are in the + wrong order. So we put back the options and non options in original + order by reversing them again. For example: + original input: a b c -x -y + reverse all: -y -x c b a + reverse options: -x -y c b a + reverse non options: -x -y a b c +*/ + +#if __STDC__ || defined(PROTO) +static void exchange(char **argv); +#endif + +static void exchange(char **argv) +{ + char *temp, **first, **last; + + /* Reverse all the elements [first_nonopt, optind) */ + first = &argv[first_nonopt]; + last = &argv[optind - 1]; + while (first < last) { + temp = *first; + *first = *last; + *last = temp; + first++; + last--; + } + /* Put back the options in order */ + first = &argv[first_nonopt]; + first_nonopt += (optind - last_nonopt); + last = &argv[first_nonopt - 1]; + while (first < last) { + temp = *first; + *first = *last; + *last = temp; + first++; + last--; + } + + /* Put back the non options in order */ + first = &argv[first_nonopt]; + last_nonopt = optind; + last = &argv[last_nonopt - 1]; + while (first < last) { + temp = *first; + *first = *last; + *last = temp; + first++; + last--; + } +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns `EOF'. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return BAD_OPTION after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return BAD_OPTION. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int _getopt_internal(int argc, char *const *argv, const char *optstring, + const struct option *longopts, int *longind, int long_only) +{ + int option_index; + + optarg = 0; + + /* Initialize the internal data when the first call is made. + Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + if (optind == 0) { + first_nonopt = last_nonopt = optind = 1; + + nextchar = NULL; + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') { + ordering = RETURN_IN_ORDER; + ++optstring; + } else if (optstring[0] == '+') { + ordering = REQUIRE_ORDER; + ++optstring; + } else if (getenv("POSIXLY_CORRECT") != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + } + + if (nextchar == NULL || *nextchar == '\0') { + if (ordering == PERMUTE) { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Now skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc && (argv[optind][0] != '-' || argv[optind][1] == '\0') +#ifdef GETOPT_COMPAT + && (longopts == NULL + || argv[optind][0] != '+' || argv[optind][1] == '\0') +#endif /* GETOPT_COMPAT */ + ) + optind++; + last_nonopt = optind; + } + + /* Special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp(argv[optind], "--")) { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return EOF; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if ((argv[optind][0] != '-' || argv[optind][1] == '\0') +#ifdef GETOPT_COMPAT + && (longopts == NULL || argv[optind][0] != '+' || argv[optind][1] == '\0') +#endif /* GETOPT_COMPAT */ + ) { + if (ordering == REQUIRE_ORDER) + return EOF; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Start decoding its characters. */ + + nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); + } + + if (longopts != NULL && ((argv[optind][0] == '-' && (argv[optind][1] == '-' || long_only)) +#ifdef GETOPT_COMPAT + || argv[optind][0] == '+' +#endif /* GETOPT_COMPAT */ + )) { + const struct option *p; + char *s = nextchar; + int exact = 0; + int ambig = 0; + const struct option *pfound = NULL; + int indfound = 0; + + while (*s && *s != '=') + s++; + + /* Test all options for either exact match or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp(p->name, nextchar, s - nextchar)) { + if (s - nextchar == my_strlen(p->name)) { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } else if (pfound == NULL) { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } else + /* Second nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) { + if (opterr) + fprintf(stderr, "%s: option `%s' is ambiguous\n", + argv[0], argv[optind]); + nextchar += my_strlen(nextchar); + optind++; + return BAD_OPTION; + } + + if (pfound != NULL) { + option_index = indfound; + optind++; + if (*s) { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = s + 1; + else { + if (opterr) { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf(stderr, + "%s: option `--%s' doesn't allow an argument\n", + argv[0], pfound->name); + else + /* +option or -option */ + fprintf(stderr, + "%s: option `%c%s' doesn't allow an argument\n", + argv[0], argv[optind - 1][0], + pfound->name); + } + nextchar += my_strlen(nextchar); + return BAD_OPTION; + } + } else if (pfound->has_arg == 1) { + if (optind < argc) + optarg = argv[optind++]; + else { + if (opterr) + fprintf(stderr, + "%s: option `%s' requires an argument\n", + argv[0], argv[optind - 1]); + nextchar += my_strlen(nextchar); + return optstring[0] == ':' ? ':' : BAD_OPTION; + } + } + nextchar += my_strlen(nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' +#ifdef GETOPT_COMPAT + || argv[optind][0] == '+' +#endif /* GETOPT_COMPAT */ + || my_index(optstring, *nextchar) == NULL) { + if (opterr) { + if (argv[optind][1] == '-') + /* --option */ + fprintf(stderr, "%s: unrecognized option `--%s'\n", + argv[0], nextchar); + else + /* +option or -option */ + fprintf(stderr, "%s: unrecognized option `%c%s'\n", + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + return BAD_OPTION; + } + } + + /* Look at and handle the next option-character. */ + + { + char c = *nextchar++; + char *temp = my_index(optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') { + if (opterr) { +#if 0 + if (c < 040 || c >= 0177) + fprintf(stderr, + "%s: unrecognized option, character code 0%o\n", + argv[0], c); + else + fprintf(stderr, "%s: unrecognized option `-%c'\n", argv[0], + c); +#else + /* 1003.2 specifies the format of this message. */ + fprintf(stderr, "%s: illegal option -- %c\n", argv[0], c); +#endif + } + optopt = c; + return BAD_OPTION; + } + if (temp[1] == ':') { + if (temp[2] == ':') { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') { + optarg = nextchar; + optind++; + } else + optarg = 0; + nextchar = NULL; + } else { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } else if (optind == argc) { + if (opterr) { +#if 0 + fprintf(stderr, + "%s: option `-%c' requires an argument\n", + argv[0], c); +#else + /* 1003.2 specifies the format of this message. */ + fprintf(stderr, + "%s: option requires an argument -- %c\n", + argv[0], c); +#endif + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = BAD_OPTION; + } else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int getopt(int argc, char *const *argv, const char *optstring) +{ + return _getopt_internal(argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); +} + +int getopt_long(int argc, char *const *argv, const char *options, const struct option long_options, int *opt_index) +{ + return _getopt_internal(argc, argv, options, long_options, opt_index, 0); +} + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int main(int argc, char **argv) +{ + int c; + int digit_optind = 0; + + while (1) { + int this_option_optind = optind ? optind : 1; + + c = getopt(argc, argv, "abc:d:0123456789"); + if (c == EOF) + break; + + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf("option %c\n", c); + break; + + case 'a': + printf("option a\n"); + break; + + case 'b': + printf("option b\n"); + break; + + case 'c': + printf("option c with value `%s'\n", optarg); + break; + + case BAD_OPTION: + break; + + default: + printf("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) { + printf("non-option ARGV-elements: "); + while (optind < argc) + printf("%s ", argv[optind++]); + printf("\n"); + } + + exit(0); +} + +#endif /* TEST */ + +#endif /* HAVE_GETOPT */ diff --git a/subst/getopt.h b/subst/getopt.h new file mode 100644 index 00000000..0abce6e9 --- /dev/null +++ b/subst/getopt.h @@ -0,0 +1,127 @@ +/* Declarations for getopt. + Copyright (C) 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc. + + 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef _GETOPT_H +#define _GETOPT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +#if __STDC__ + const char *name; +#else + char *name; +#endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +#if __STDC__ || defined(PROTO) +#if defined(__GNU_LIBRARY__) +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt (int argc, char *const *argv, const char *shortopts); +#endif /* not __GNU_LIBRARY__ */ +extern int getopt_long (int argc, char *const *argv, const char *shortopts, + const struct option *longopts, int *longind); +extern int getopt_long_only (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); +#else /* not __STDC__ */ +extern int getopt (); +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +#endif /* not __STDC__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* _GETOPT_H */ diff --git a/subst/gettimeofday.c b/subst/gettimeofday.c new file mode 100644 index 00000000..aa133ba6 --- /dev/null +++ b/subst/gettimeofday.c @@ -0,0 +1,67 @@ +/* + * $PostgreSQL: /cvsroot/pgsql-server/src/port/gettimeofday.c,v 1.3 2003/11/29 19:52:13 pgsql Exp $ + * + * Copyright (c) 2003 SRA, Inc. + * Copyright (c) 2003 SKC, Inc. + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose, without fee, and without a + * written agreement is hereby granted, provided that the above + * copyright notice and this paragraph and the following two + * paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, + * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS + * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, + * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef HAVE_GETTIMEOFDAY + +#include "ac-stdint.h" + +#include +#include +#include +#include +#include + +#include "subst.h" + +/* FILETIME of Jan 1 1970 00:00:00. */ +static const unsigned __int64 epoch = 116444736000000000L; + +/* + * timezone information is stored outside the kernel so tzp isn't used anymore. + */ + +int +gettimeofday(struct timeval * tp, struct timezone * tzp) +{ + FILETIME file_time; + SYSTEMTIME system_time; + ULARGE_INTEGER ularge; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + ularge.LowPart = file_time.dwLowDateTime; + ularge.HighPart = file_time.dwHighDateTime; + + tp->tv_sec = (long) ((ularge.QuadPart - epoch) / 10000000L); + tp->tv_usec = (long) (system_time.wMilliseconds * 1000); + + return 0; +} + +#endif diff --git a/subst/inet_aton.c b/subst/inet_aton.c new file mode 100644 index 00000000..8bb44106 --- /dev/null +++ b/subst/inet_aton.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Kungliga Tekniska + * Högskolan and its contributors. + * + * 4. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* $Id: inet_aton.c,v 1.5 2005/06/02 04:48:25 zion Exp $ */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if !defined(HAVE_INET_ATON) && defined(WIN32) + +#include "ac-stdint.h" + +#include "ip6_misc.h" + +/* Minimal implementation of inet_aton. + * Cannot distinguish between failure and a local broadcast address. */ + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +int +inet_aton(const char *cp, struct in_addr *addr) +{ + addr->s_addr = inet_addr(cp); + return (addr->s_addr == INADDR_NONE) ? 0 : 1; +} + +#endif diff --git a/subst/inet_ntop.c b/subst/inet_ntop.c new file mode 100644 index 00000000..2a4ac47b --- /dev/null +++ b/subst/inet_ntop.c @@ -0,0 +1,65 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if !defined(HAVE_INET_NTOP) && defined(WIN32) + +#include "ac-stdint.h" + +#include "ip6_misc.h" + +#include +#include + +#ifndef INET_ADDRSTRLEN +#define INET_ADDRSTRLEN 16 +#endif + +static const char * +inet_ntop_v4 (const void *src, char *dst, size_t size) +{ + const char digits[] = "0123456789"; + int i; + struct in_addr *addr = (struct in_addr *)src; + u_long a = ntohl(addr->s_addr); + const char *orig_dst = dst; + + if (size < INET_ADDRSTRLEN) { + errno = ENOSPC; + return NULL; + } + for (i = 0; i < 4; ++i) { + int n = (a >> (24 - i * 8)) & 0xFF; + int non_zerop = 0; + + if (non_zerop || n / 100 > 0) { + *dst++ = digits[n / 100]; + n %= 100; + non_zerop = 1; + } + if (non_zerop || n / 10 > 0) { + *dst++ = digits[n / 10]; + n %= 10; + non_zerop = 1; + } + *dst++ = digits[n]; + if (i != 3) + *dst++ = '.'; + } + *dst++ = '\0'; + return orig_dst; +} + +const char * +inet_ntop(int af, const void *src, char *dst, size_t size) +{ + switch (af) { + case AF_INET : + return inet_ntop_v4 (src, dst, size); + default : + errno = WSAEAFNOSUPPORT; + return NULL; + } +} + +#endif diff --git a/subst/inet_pton.c b/subst/inet_pton.c new file mode 100644 index 00000000..b1565867 --- /dev/null +++ b/subst/inet_pton.c @@ -0,0 +1,27 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if !defined(HAVE_INET_PTON) && defined(WIN32) + +#include "ac-stdint.h" + +#include "ip6_misc.h" + +#include + +#ifndef EAFNOSUPPORT +#define EAFNOSUPPORT 97 /* not present in errno.h provided with VC */ +#endif + +int +inet_pton(int af, const char *src, void *dst) +{ + if (af != AF_INET) { + errno = EAFNOSUPPORT; + return -1; + } + return inet_aton (src, dst); +} + +#endif diff --git a/subst/ip6_misc.h b/subst/ip6_misc.h new file mode 100644 index 00000000..09053185 --- /dev/null +++ b/subst/ip6_misc.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 1993, 1994, 1997 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that: (1) source code distributions + * retain the above copyright notice and this paragraph in its entirety, (2) + * distributions including binary code include the above copyright notice and + * this paragraph in its entirety in the documentation or other materials + * provided with the distribution, and (3) all advertising materials mentioning + * features or use of this software display the following acknowledgement: + * ``This product includes software developed by the University of California, + * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of + * the University nor the names of its contributors may be used to endorse + * or promote products derived from this software without specific prior + * written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * @(#) $Header: /home/cvs/jabberd2/subst/ip6_misc.h,v 1.5 2005/06/02 04:48:25 zion Exp $ (LBL) + */ + +/* + * This file contains a collage of declarations for IPv6 from FreeBSD not present in Windows + */ + +#include + +#ifndef __MINGW32__ +#include +#endif /* __MINGW32__ */ + +#ifndef IN_MULTICAST +#define IN_MULTICAST(a) IN_CLASSD(a) +#endif + +#ifndef IN_EXPERIMENTAL +#define IN_EXPERIMENTAL(a) ((((uint32_t) (a)) & 0xe0000000) == 0xe0000000) +#endif + +#ifndef IN_LOOPBACK +#define IN_LOOPBACKNET 127 +#endif + +#ifdef __MINGW32__ +/* IPv6 address */ +struct in6_addr + { + union + { + uint8_t u6_addr8[16]; + uint16_t u6_addr16[8]; + uint32_t u6_addr32[4]; + } in6_u; +#define s6_addr in6_u.u6_addr8 +#define s6_addr16 in6_u.u6_addr16 +#define s6_addr32 in6_u.u6_addr32 +#define s6_addr64 in6_u.u6_addr64 + }; + +#define IN6ADDR_ANY_INIT { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } +#define IN6ADDR_LOOPBACK_INIT { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } +#endif /* __MINGW32__ */ + + +#ifdef __MINGW32__ +typedef unsigned short sa_family_t; + +#define __SOCKADDR_COMMON(sa_prefix) \ + sa_family_t sa_prefix##family + +/* Ditto, for IPv6. */ +struct sockaddr_in6 + { + __SOCKADDR_COMMON (sin6_); + uint16_t sin6_port; /* Transport layer port # */ + uint32_t sin6_flowinfo; /* IPv6 flow information */ + struct in6_addr sin6_addr; /* IPv6 address */ + }; + +#define IN6_IS_ADDR_V4MAPPED(a) \ + ((((uint32_t *) (a))[0] == 0) && (((uint32_t *) (a))[1] == 0) && \ + (((uint32_t *) (a))[2] == htonl (0xffff))) + +#define IN6_IS_ADDR_MULTICAST(a) (((uint8_t *) (a))[0] == 0xff) + +#define IN6_IS_ADDR_LINKLOCAL(a) \ + ((((uint32_t *) (a))[0] & htonl (0xffc00000)) == htonl (0xfe800000)) + +#define IN6_IS_ADDR_LOOPBACK(a) \ + (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ + ((uint32_t *) (a))[2] == 0 && ((uint32_t *) (a))[3] == htonl (1)) +#endif /* __MINGW32__ */ + +#define ip6_vfc ip6_ctlun.ip6_un2_vfc +#define ip6_flow ip6_ctlun.ip6_un1.ip6_un1_flow +#define ip6_plen ip6_ctlun.ip6_un1.ip6_un1_plen +#define ip6_nxt ip6_ctlun.ip6_un1.ip6_un1_nxt +#define ip6_hlim ip6_ctlun.ip6_un1.ip6_un1_hlim +#define ip6_hops ip6_ctlun.ip6_un1.ip6_un1_hlim + +#define nd_rd_type nd_rd_hdr.icmp6_type +#define nd_rd_code nd_rd_hdr.icmp6_code +#define nd_rd_cksum nd_rd_hdr.icmp6_cksum +#define nd_rd_reserved nd_rd_hdr.icmp6_data32[0] + +/* + * IPV6 extension headers + */ +#define IPPROTO_HOPOPTS 0 /* IPv6 hop-by-hop options */ +#define IPPROTO_IPV6 41 /* IPv6 header. */ +#define IPPROTO_ROUTING 43 /* IPv6 routing header */ +#define IPPROTO_FRAGMENT 44 /* IPv6 fragmentation header */ +#define IPPROTO_ESP 50 /* encapsulating security payload */ +#define IPPROTO_AH 51 /* authentication header */ +#define IPPROTO_ICMPV6 58 /* ICMPv6 */ +#define IPPROTO_NONE 59 /* IPv6 no next header */ +#define IPPROTO_DSTOPTS 60 /* IPv6 destination options */ +#define IPPROTO_PIM 103 /* Protocol Independent Multicast. */ + +#define IPV6_RTHDR_TYPE_0 0 + +/* Option types and related macros */ +#define IP6OPT_PAD1 0x00 /* 00 0 00000 */ +#define IP6OPT_PADN 0x01 /* 00 0 00001 */ +#define IP6OPT_JUMBO 0xC2 /* 11 0 00010 = 194 */ +#define IP6OPT_JUMBO_LEN 6 +#define IP6OPT_ROUTER_ALERT 0x05 /* 00 0 00101 */ + +#define IP6OPT_RTALERT_LEN 4 +#define IP6OPT_RTALERT_MLD 0 /* Datagram contains an MLD message */ +#define IP6OPT_RTALERT_RSVP 1 /* Datagram contains an RSVP message */ +#define IP6OPT_RTALERT_ACTNET 2 /* contains an Active Networks msg */ +#define IP6OPT_MINLEN 2 + +#define IP6OPT_BINDING_UPDATE 0xc6 /* 11 0 00110 */ +#define IP6OPT_BINDING_ACK 0x07 /* 00 0 00111 */ +#define IP6OPT_BINDING_REQ 0x08 /* 00 0 01000 */ +#define IP6OPT_HOME_ADDRESS 0xc9 /* 11 0 01001 */ +#define IP6OPT_EID 0x8a /* 10 0 01010 */ + +#define IP6OPT_TYPE(o) ((o) & 0xC0) +#define IP6OPT_TYPE_SKIP 0x00 +#define IP6OPT_TYPE_DISCARD 0x40 +#define IP6OPT_TYPE_FORCEICMP 0x80 +#define IP6OPT_TYPE_ICMP 0xC0 + +#define IP6OPT_MUTABLE 0x20 + + +#ifdef __MINGW32__ +#ifndef EAI_ADDRFAMILY +struct addrinfo { + int ai_flags; /* AI_PASSIVE, AI_CANONNAME */ + int ai_family; /* PF_xxx */ + int ai_socktype; /* SOCK_xxx */ + int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ + size_t ai_addrlen; /* length of ai_addr */ + char *ai_canonname; /* canonical name for hostname */ + struct sockaddr *ai_addr; /* binary address */ + struct addrinfo *ai_next; /* next structure in linked list */ +}; +#endif +#endif /* __MINGW32__ */ diff --git a/subst/snprintf.c b/subst/snprintf.c new file mode 100644 index 00000000..13fb1de2 --- /dev/null +++ b/subst/snprintf.c @@ -0,0 +1,961 @@ +/* ==================================================================== + * Copyright (c) 1995-1998 The Apache Group. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the Apache Group + * 4. The names "Apache Server" and "Apache Group" must not be used to + * endorse or promote products derived from this software without + * prior written permission. + * + * 5. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Group and was originally based + * on public domain software written at the National Center for + * Supercomputing Applications, University of Illinois, Urbana-Champaign. + * For more information on the Apache Group and the Apache HTTP server + * project, please see . + * + * This code is based on, and used with the permission of, the + * SIO stdio-replacement strx_* functions by Panos Tsirigotis + * for xinetd. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if !defined(HAVE_SNPRINTF) || defined(HAVE_BROKEN_SNPRINTF) || !defined(HAVE_VSNPRINTF) || defined(HAVE_BROKEN_VSNPRINTF) + +#include +#include +#include +#include +#include +#include +#include + + +#ifdef HAVE_GCVT + +#define ap_ecvt ecvt +#define ap_fcvt fcvt +#define ap_gcvt gcvt + +#else + +/* +* cvt.c - IEEE floating point formatting routines for FreeBSD +* from GNU libc-4.6.27 +*/ + +/* +* ap_ecvt converts to decimal +* the number of digits is specified by ndigit +* decpt is set to the position of the decimal point +* sign is set to 0 for positive, 1 for negative +*/ + +#define NDIG 80 + +static char * +ap_cvt(double arg, int ndigits, int *decpt, int *sign, int eflag) +{ + register int r2; + double fi, fj; + register char *p, *p1; + static char buf[NDIG]; + + if (ndigits >= NDIG - 1) + ndigits = NDIG - 2; + r2 = 0; + *sign = 0; + p = &buf[0]; + if (arg < 0) { + *sign = 1; + arg = -arg; + } + arg = modf(arg, &fi); + p1 = &buf[NDIG]; + /* + * Do integer part + */ + if (fi != 0) { + p1 = &buf[NDIG]; + while (fi != 0) { + fj = modf(fi / 10, &fi); + *--p1 = (int) ((fj + .03) * 10) + '0'; + r2++; + } + while (p1 < &buf[NDIG]) + *p++ = *p1++; + } else if (arg > 0) { + while ((fj = arg * 10) < 1) { + arg = fj; + r2--; + } + } + p1 = &buf[ndigits]; + if (eflag == 0) + p1 += r2; + *decpt = r2; + if (p1 < &buf[0]) { + buf[0] = '\0'; + return (buf); + } + while (p <= p1 && p < &buf[NDIG]) { + arg *= 10; + arg = modf(arg, &fj); + *p++ = (int) fj + '0'; + } + if (p1 >= &buf[NDIG]) { + buf[NDIG - 1] = '\0'; + return (buf); + } + p = p1; + *p1 += 5; + while (*p1 > '9') { + *p1 = '0'; + if (p1 > buf) + ++ * --p1; + else { + *p1 = '1'; + (*decpt)++; + if (eflag == 0) { + if (p > buf) + *p = '0'; + p++; + } + } + } + *p = '\0'; + return (buf); +} + +static char * +ap_ecvt(double arg, int ndigits, int *decpt, int *sign) +{ + return (ap_cvt(arg, ndigits, decpt, sign, 1)); +} + +static char * +ap_fcvt(double arg, int ndigits, int *decpt, int *sign) +{ + return (ap_cvt(arg, ndigits, decpt, sign, 0)); +} + +/* +* ap_gcvt - Floating output conversion to +* minimal length string +*/ + +static char * +ap_gcvt(double number, int ndigit, char *buf) +{ + int sign, decpt; + register char *p1, *p2; + int i; + + p1 = ap_ecvt(number, ndigit, &decpt, &sign); + p2 = buf; + if (sign) + *p2++ = '-'; + for (i = ndigit - 1; i > 0 && p1[i] == '0'; i--) + ndigit--; + if ((decpt >= 0 && decpt - ndigit > 4) + || (decpt < 0 && decpt < -3)) { /* use E-style */ + decpt--; + *p2++ = *p1++; + *p2++ = '.'; + for (i = 1; i < ndigit; i++) + *p2++ = *p1++; + *p2++ = 'e'; + if (decpt < 0) { + decpt = -decpt; + *p2++ = '-'; + } else + *p2++ = '+'; + if (decpt / 100 > 0) + *p2++ = decpt / 100 + '0'; + if (decpt / 10 > 0) + *p2++ = (decpt % 100) / 10 + '0'; + *p2++ = decpt % 10 + '0'; + } else { + if (decpt <= 0) { + if (*p1 != '0') + *p2++ = '.'; + while (decpt < 0) { + decpt++; + *p2++ = '0'; + } + } + for (i = 1; i <= ndigit; i++) { + *p2++ = *p1++; + if (i == decpt) + *p2++ = '.'; + } + if (ndigit < decpt) { + while (ndigit++ < decpt) + *p2++ = '0'; + *p2++ = '.'; + } + } + if (p2[-1] == '.') + p2--; + *p2 = '\0'; + return (buf); +} + +#endif /* HAVE_CVT */ + +typedef enum { + NO = 0, YES = 1 +} boolean_e; + +#define FALSE 0 +#define TRUE 1 +#define NUL '\0' +#define INT_NULL ((int *)0) +#define WIDE_INT long + +typedef WIDE_INT wide_int; +typedef unsigned WIDE_INT u_wide_int; +typedef int bool_int; + +#define S_NULL "(null)" +#define S_NULL_LEN 6 + +#define FLOAT_DIGITS 6 +#define EXPONENT_LENGTH 10 + +/* + * NUM_BUF_SIZE is the size of the buffer used for arithmetic conversions + * + * XXX: this is a magic number; do not decrease it + */ +#define NUM_BUF_SIZE 512 + + +/* + * Descriptor for buffer area + */ +struct buf_area { + char *buf_end; + char *nextb; /* pointer to next byte to read/write */ +}; + +typedef struct buf_area buffy; + +/* + * The INS_CHAR macro inserts a character in the buffer and writes + * the buffer back to disk if necessary + * It uses the char pointers sp and bep: + * sp points to the next available character in the buffer + * bep points to the end-of-buffer+1 + * While using this macro, note that the nextb pointer is NOT updated. + * + * NOTE: Evaluation of the c argument should not have any side-effects + */ +#define INS_CHAR( c, sp, bep, cc ) \ + { \ + if ( sp < bep ) \ + { \ + *sp++ = c ; \ + cc++ ; \ + } \ + } + +#define NUM( c ) ( c - '0' ) + +#define STR_TO_DEC( str, num ) \ + num = NUM( *str++ ) ; \ + while ( isdigit((int)*str ) ) \ + { \ + num *= 10 ; \ + num += NUM( *str++ ) ; \ + } + +/* + * This macro does zero padding so that the precision + * requirement is satisfied. The padding is done by + * adding '0's to the left of the string that is going + * to be printed. + */ +#define FIX_PRECISION( adjust, precision, s, s_len ) \ + if ( adjust ) \ + while ( s_len < precision ) \ + { \ + *--s = '0' ; \ + s_len++ ; \ + } + +/* + * Macro that does padding. The padding is done by printing + * the character ch. + */ +#define PAD( width, len, ch ) do \ + { \ + INS_CHAR( ch, sp, bep, cc ) ; \ + width-- ; \ + } \ + while ( width > len ) + +/* + * Prefix the character ch to the string str + * Increase length + * Set the has_prefix flag + */ +#define PREFIX( str, length, ch ) *--str = ch ; length++ ; has_prefix = YES + + +/* + * Convert num to its decimal format. + * Return value: + * - a pointer to a string containing the number (no sign) + * - len contains the length of the string + * - is_negative is set to TRUE or FALSE depending on the sign + * of the number (always set to FALSE if is_unsigned is TRUE) + * + * The caller provides a buffer for the string: that is the buf_end argument + * which is a pointer to the END of the buffer + 1 (i.e. if the buffer + * is declared as buf[ 100 ], buf_end should be &buf[ 100 ]) + */ +static char * +conv_10(register wide_int num, register bool_int is_unsigned, + register bool_int * is_negative, char *buf_end, register int *len) +{ + register char *p = buf_end; + register u_wide_int magnitude; + + if (is_unsigned) { + magnitude = (u_wide_int) num; + *is_negative = FALSE; + } else { + *is_negative = (num < 0); + + /* + * On a 2's complement machine, negating the most negative integer + * results in a number that cannot be represented as a signed integer. + * Here is what we do to obtain the number's magnitude: + * a. add 1 to the number + * b. negate it (becomes positive) + * c. convert it to unsigned + * d. add 1 + */ + if (*is_negative) { + wide_int t = num + 1; + + magnitude = ((u_wide_int) - t) + 1; + } else + magnitude = (u_wide_int) num; + } + + /* + * We use a do-while loop so that we write at least 1 digit + */ + do { + register u_wide_int new_magnitude = magnitude / 10; + + *--p = magnitude - new_magnitude * 10 + '0'; + magnitude = new_magnitude; + } + while (magnitude); + + *len = buf_end - p; + return (p); +} + + + +/* + * Convert a floating point number to a string formats 'f', 'e' or 'E'. + * The result is placed in buf, and len denotes the length of the string + * The sign is returned in the is_negative argument (and is not placed + * in buf). + */ +static char * +conv_fp(register char format, register double num, + boolean_e add_dp, int precision, bool_int * is_negative, char *buf, int *len) +{ + register char *s = buf; + register char *p; + int decimal_point; + + if (format == 'f') + p = ap_fcvt(num, precision, &decimal_point, is_negative); + else /* either e or E format */ + p = ap_ecvt(num, precision + 1, &decimal_point, is_negative); + + /* + * Check for Infinity and NaN + */ + if (isalpha((int)*p)) { + *len = strlen(strcpy(buf, p)); + *is_negative = FALSE; + return (buf); + } + if (format == 'f') { + if (decimal_point <= 0) { + *s++ = '0'; + if (precision > 0) { + *s++ = '.'; + while (decimal_point++ < 0) + *s++ = '0'; + } else if (add_dp) { + *s++ = '.'; + } + } else { + while (decimal_point-- > 0) { + *s++ = *p++; + } + if (precision > 0 || add_dp) { + *s++ = '.'; + } + } + } else { + *s++ = *p++; + if (precision > 0 || add_dp) + *s++ = '.'; + } + + /* + * copy the rest of p, the NUL is NOT copied + */ + while (*p) + *s++ = *p++; + + if (format != 'f') { + char temp[EXPONENT_LENGTH]; /* for exponent conversion */ + int t_len; + bool_int exponent_is_negative; + + *s++ = format; /* either e or E */ + decimal_point--; + if (decimal_point != 0) { + p = conv_10((wide_int) decimal_point, FALSE, &exponent_is_negative, + &temp[EXPONENT_LENGTH], &t_len); + *s++ = exponent_is_negative ? '-' : '+'; + + /* + * Make sure the exponent has at least 2 digits + */ + if (t_len == 1) + *s++ = '0'; + while (t_len--) + *s++ = *p++; + } else { + *s++ = '+'; + *s++ = '0'; + *s++ = '0'; + } + } + *len = s - buf; + return (buf); +} + + +/* + * Convert num to a base X number where X is a power of 2. nbits determines X. + * For example, if nbits is 3, we do base 8 conversion + * Return value: + * a pointer to a string containing the number + * + * The caller provides a buffer for the string: that is the buf_end argument + * which is a pointer to the END of the buffer + 1 (i.e. if the buffer + * is declared as buf[ 100 ], buf_end should be &buf[ 100 ]) + */ +static char * +conv_p2(register u_wide_int num, register int nbits, + char format, char *buf_end, register int *len) +{ + register int mask = (1 << nbits) - 1; + register char *p = buf_end; + static char low_digits[] = "0123456789abcdef"; + static char upper_digits[] = "0123456789ABCDEF"; + register char *digits = (format == 'X') ? upper_digits : low_digits; + + do { + *--p = digits[num & mask]; + num >>= nbits; + } + while (num); + + *len = buf_end - p; + return (p); +} + + +/* + * Do format conversion placing the output in buffer + */ +static int format_converter(register buffy * odp, const char *fmt, + va_list ap) +{ + register char *sp; + register char *bep; + register int cc = 0; + register int i; + + register char *s = NULL; + char *q; + int s_len; + + register int min_width = 0; + int precision = 0; + enum { + LEFT, RIGHT + } adjust; + char pad_char; + char prefix_char; + + double fp_num; + wide_int i_num = (wide_int) 0; + u_wide_int ui_num; + + char num_buf[NUM_BUF_SIZE]; + char char_buf[2]; /* for printing %% and % */ + + /* + * Flag variables + */ + boolean_e is_long; + boolean_e alternate_form; + boolean_e print_sign; + boolean_e print_blank; + boolean_e adjust_precision; + boolean_e adjust_width; + bool_int is_negative; + + sp = odp->nextb; + bep = odp->buf_end; + + while (*fmt) { + if (*fmt != '%') { + INS_CHAR(*fmt, sp, bep, cc); + } else { + /* + * Default variable settings + */ + adjust = RIGHT; + alternate_form = print_sign = print_blank = NO; + pad_char = ' '; + prefix_char = NUL; + + fmt++; + + /* + * Try to avoid checking for flags, width or precision + */ + if (isascii((int)*fmt) && !islower((int)*fmt)) { + /* + * Recognize flags: -, #, BLANK, + + */ + for (;; fmt++) { + if (*fmt == '-') + adjust = LEFT; + else if (*fmt == '+') + print_sign = YES; + else if (*fmt == '#') + alternate_form = YES; + else if (*fmt == ' ') + print_blank = YES; + else if (*fmt == '0') + pad_char = '0'; + else + break; + } + + /* + * Check if a width was specified + */ + if (isdigit((int)*fmt)) { + STR_TO_DEC(fmt, min_width); + adjust_width = YES; + } else if (*fmt == '*') { + min_width = va_arg(ap, int); + fmt++; + adjust_width = YES; + if (min_width < 0) { + adjust = LEFT; + min_width = -min_width; + } + } else + adjust_width = NO; + + /* + * Check if a precision was specified + * + * XXX: an unreasonable amount of precision may be specified + * resulting in overflow of num_buf. Currently we + * ignore this possibility. + */ + if (*fmt == '.') { + adjust_precision = YES; + fmt++; + if (isdigit((int)*fmt)) { + STR_TO_DEC(fmt, precision); + } else if (*fmt == '*') { + precision = va_arg(ap, int); + fmt++; + if (precision < 0) + precision = 0; + } else + precision = 0; + } else + adjust_precision = NO; + } else + adjust_precision = adjust_width = NO; + + /* + * Modifier check + */ + if (*fmt == 'l') { + is_long = YES; + fmt++; + } else + is_long = NO; + + /* + * Argument extraction and printing. + * First we determine the argument type. + * Then, we convert the argument to a string. + * On exit from the switch, s points to the string that + * must be printed, s_len has the length of the string + * The precision requirements, if any, are reflected in s_len. + * + * NOTE: pad_char may be set to '0' because of the 0 flag. + * It is reset to ' ' by non-numeric formats + */ + switch (*fmt) { + case 'u': + if (is_long) + i_num = va_arg(ap, u_wide_int); + else + i_num = (wide_int) va_arg(ap, unsigned int); + /* + * The rest also applies to other integer formats, so fall + * into that case. + */ + case 'd': + case 'i': + /* + * Get the arg if we haven't already. + */ + if ((*fmt) != 'u') { + if (is_long) + i_num = va_arg(ap, wide_int); + else + i_num = (wide_int) va_arg(ap, int); + }; + s = conv_10(i_num, (*fmt) == 'u', &is_negative, + &num_buf[NUM_BUF_SIZE], &s_len); + FIX_PRECISION(adjust_precision, precision, s, s_len); + + if (*fmt != 'u') { + if (is_negative) + prefix_char = '-'; + else if (print_sign) + prefix_char = '+'; + else if (print_blank) + prefix_char = ' '; + } + break; + + + case 'o': + if (is_long) + ui_num = va_arg(ap, u_wide_int); + else + ui_num = (u_wide_int) va_arg(ap, unsigned int); + s = conv_p2(ui_num, 3, *fmt, + &num_buf[NUM_BUF_SIZE], &s_len); + FIX_PRECISION(adjust_precision, precision, s, s_len); + if (alternate_form && *s != '0') { + *--s = '0'; + s_len++; + } + break; + + + case 'x': + case 'X': + if (is_long) + ui_num = (u_wide_int) va_arg(ap, u_wide_int); + else + ui_num = (u_wide_int) va_arg(ap, unsigned int); + s = conv_p2(ui_num, 4, *fmt, + &num_buf[NUM_BUF_SIZE], &s_len); + FIX_PRECISION(adjust_precision, precision, s, s_len); + if (alternate_form && i_num != 0) { + *--s = *fmt; /* 'x' or 'X' */ + *--s = '0'; + s_len += 2; + } + break; + + + case 's': + s = va_arg(ap, char *); + if (s != NULL) { + if (!adjust_precision) { + s_len = strlen(s); + } + else { + /* From the C library standard in section 7.9.6.1: + * ...if the precision is specified, no more then + * that many characters are written. If the + * precision is not specified or is greater + * than the size of the array, the array shall + * contain a null character. + * + * My reading is is precision is specified and + * is less then or equal to the size of the + * array, no null character is required. So + * we can't do a strlen. + * + * This figures out the length of the string + * up to the precision. Once it's long enough + * for the specified precision, we don't care + * anymore. + * + * NOTE: you must do the length comparison + * before the check for the null character. + * Otherwise, you'll check one beyond the + * last valid character. + */ + const char *walk; + + for (walk = s, s_len = 0; + (s_len < precision) && (*walk != '\0'); + ++walk, ++s_len); + } + } else { + s = S_NULL; + s_len = S_NULL_LEN; + } + pad_char = ' '; + break; + + + case 'f': + case 'e': + case 'E': + fp_num = va_arg(ap, double); + + s = conv_fp(*fmt, fp_num, alternate_form, + (adjust_precision == NO) ? FLOAT_DIGITS : precision, + &is_negative, &num_buf[1], &s_len); + if (is_negative) + prefix_char = '-'; + else if (print_sign) + prefix_char = '+'; + else if (print_blank) + prefix_char = ' '; + break; + + + case 'g': + case 'G': + if (adjust_precision == NO) + precision = FLOAT_DIGITS; + else if (precision == 0) + precision = 1; + /* + * * We use &num_buf[ 1 ], so that we have room for the sign + */ + s = ap_gcvt(va_arg(ap, double), precision, &num_buf[1]); + if (*s == '-') + prefix_char = *s++; + else if (print_sign) + prefix_char = '+'; + else if (print_blank) + prefix_char = ' '; + + s_len = strlen(s); + + if (alternate_form && (q = strchr(s, '.')) == NULL) + s[s_len++] = '.'; + if (*fmt == 'G' && (q = strchr(s, 'e')) != NULL) + *q = 'E'; + break; + + + case 'c': + char_buf[0] = (char) (va_arg(ap, int)); + s = &char_buf[0]; + s_len = 1; + pad_char = ' '; + break; + + + case '%': + char_buf[0] = '%'; + s = &char_buf[0]; + s_len = 1; + pad_char = ' '; + break; + + + case 'n': + *(va_arg(ap, int *)) = cc; + break; + + /* + * Always extract the argument as a "char *" pointer. We + * should be using "void *" but there are still machines + * that don't understand it. + * If the pointer size is equal to the size of an unsigned + * integer we convert the pointer to a hex number, otherwise + * we print "%p" to indicate that we don't handle "%p". + */ + case 'p': + ui_num = (u_wide_int) va_arg(ap, char *); + + if (sizeof(char *) <= sizeof(u_wide_int)) + s = conv_p2(ui_num, 4, 'x', + &num_buf[NUM_BUF_SIZE], &s_len); + else { + s = "%p"; + s_len = 2; + } + pad_char = ' '; + break; + + + case NUL: + /* + * The last character of the format string was %. + * We ignore it. + */ + continue; + + + /* + * The default case is for unrecognized %'s. + * We print % to help the user identify what + * option is not understood. + * This is also useful in case the user wants to pass + * the output of format_converter to another function + * that understands some other % (like syslog). + * Note that we can't point s inside fmt because the + * unknown could be preceded by width etc. + */ + default: + char_buf[0] = '%'; + char_buf[1] = *fmt; + s = char_buf; + s_len = 2; + pad_char = ' '; + break; + } + + if (prefix_char != NUL) { + *--s = prefix_char; + s_len++; + } + if (adjust_width && adjust == RIGHT && min_width > s_len) { + if (pad_char == '0' && prefix_char != NUL) { + INS_CHAR(*s, sp, bep, cc) + s++; + s_len--; + min_width--; + } + PAD(min_width, s_len, pad_char); + } + /* + * Print the string s. + */ + for (i = s_len; i != 0; i--) { + INS_CHAR(*s, sp, bep, cc); + s++; + } + + if (adjust_width && adjust == LEFT && min_width > s_len) + PAD(min_width, s_len, pad_char); + } + fmt++; + } + odp->nextb = sp; + return (cc); +} + + +/* + * This is the general purpose conversion function. + */ +static void strx_printv(int *ccp, char *buf, size_t len, const char *format, + va_list ap) +{ + buffy od; + int cc; + + /* + * First initialize the descriptor + * Notice that if no length is given, we initialize buf_end to the + * highest possible address. + */ + od.buf_end = len ? &buf[len] : (char *) ~0; + od.nextb = buf; + + /* + * Do the conversion + */ + cc = format_converter(&od, format, ap); + if (len == 0 || od.nextb <= od.buf_end) + *(od.nextb) = '\0'; + if (ccp) + *ccp = cc; +} + + +int ap_snprintf(char *buf, size_t len, const char *format,...) +{ + int cc; + va_list ap; + + va_start(ap, format); + strx_printv(&cc, buf, (len - 1), format, ap); + va_end(ap); + return (cc); +} + + +int ap_vsnprintf(char *buf, size_t len, const char *format, va_list ap) +{ + int cc; + + strx_printv(&cc, buf, (len - 1), format, ap); + return (cc); +} + +#endif /* HAVE_SNPRINTF */ diff --git a/subst/subst.h b/subst/subst.h new file mode 100644 index 00000000..e4e36291 --- /dev/null +++ b/subst/subst.h @@ -0,0 +1,91 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* substituted functions */ + +#ifndef INCL_SUBST_H +#define INCL_SUBST_H 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if !defined(HAVE_SNPRINTF) || defined(HAVE_BROKEN_SNPRINTF) + extern int ap_snprintf(char *, size_t, const char *, ...); +# define snprintf ap_snprintf +#endif + +#if !defined(HAVE_VSNPRINTF) || defined(HAVE_BROKEN_VSNPRINTF) + extern int ap_vsnprintf(char *, size_t, const char *, va_list ap); +# define vsnprintf ap_vsnprintf +#endif + +#ifndef HAVE_GETOPT +# include "getopt.h" +#endif + +#ifndef HAVE_SYSLOG_H +# include "syslog.h" +#endif + +#ifndef HAVE_GETTIMEOFDAY + +# if defined(HAVE_SYS_TIME_H) +# include +# elif defined(HAVE_SYS_TIMEB_H) +# include +# endif + +struct timezone { + int tz_minuteswest; + int tz_dsttime; +}; + +extern int gettimeofday(struct timeval *tp, struct timezone *tz); +#endif + +#ifdef HAVE_WINSOCK2_H +# include +# include "ip6_misc.h" + +# define EWOULDBLOCK WSAEWOULDBLOCK +# define ECONNREFUSED WSAECONNREFUSED +# define EINPROGRESS WSAEINPROGRESS +#endif + +#ifndef HAVE_INET_ATON +extern int inet_aton(const char *cp, struct in_addr *addr); +#endif +#ifndef HAVE_INET_NTOP +extern const char *inet_ntop(int af, const void *src, char *dst, size_t size); +#endif +#ifndef HAVE_INET_PTON +extern int inet_pton(int af, const char *src, void *dst); +#endif + +#ifndef HAVE_IN_PORT_T +typedef uint16_t in_port_t; +#endif + +#ifdef HAVE__MKDIR +# define mkdir(a,b) _mkdir(a) +#endif + +#endif diff --git a/subst/syslog.c b/subst/syslog.c new file mode 100644 index 00000000..66fbd0b8 --- /dev/null +++ b/subst/syslog.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: syslog.c,v 1.5 2005/06/02 04:48:25 zion Exp $ */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if !defined(HAVE_SYSLOG_H) && defined(HAVE_REPORTEVENT) + +#include +#include +#include +#include + +#include "syslog.h" + +static HANDLE hAppLog = NULL; +static FILE *log_stream; +static int debug_level = 0; + +static struct dsn_c_pvt_sfnt { + int val; + const char *strval; +} facilities[] = { + { LOG_KERN, "kern" }, + { LOG_USER, "user" }, + { LOG_MAIL, "mail" }, + { LOG_DAEMON, "daemon" }, + { LOG_AUTH, "auth" }, + { LOG_SYSLOG, "syslog" }, + { LOG_LPR, "lpr" }, +#ifdef LOG_NEWS + { LOG_NEWS, "news" }, +#endif +#ifdef LOG_UUCP + { LOG_UUCP, "uucp" }, +#endif +#ifdef LOG_CRON + { LOG_CRON, "cron" }, +#endif +#ifdef LOG_AUTHPRIV + { LOG_AUTHPRIV, "authpriv" }, +#endif +#ifdef LOG_FTP + { LOG_FTP, "ftp" }, +#endif + { LOG_LOCAL0, "local0"}, + { LOG_LOCAL1, "local1"}, + { LOG_LOCAL2, "local2"}, + { LOG_LOCAL3, "local3"}, + { LOG_LOCAL4, "local4"}, + { LOG_LOCAL5, "local5"}, + { LOG_LOCAL6, "local6"}, + { LOG_LOCAL7, "local7"}, + { 0, NULL } +}; + +/* + * Log to the NT Event Log + */ +void +syslog(int level, const char *fmt, ...) { + va_list ap; + char buf[1024]; + const char *str[1]; + + str[0] = buf; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + + /* Make sure that the channel is open to write the event */ + if (hAppLog != NULL) { + switch (level) { + case LOG_INFO: + case LOG_NOTICE: + case LOG_DEBUG: + ReportEvent(hAppLog, EVENTLOG_INFORMATION_TYPE, 0, + 0, NULL, 1, 0, str, NULL); + break; + case LOG_WARNING: + ReportEvent(hAppLog, EVENTLOG_WARNING_TYPE, 0, + 0, NULL, 1, 0, str, NULL); + break; + default: + ReportEvent(hAppLog, EVENTLOG_ERROR_TYPE, 0, + 0, NULL, 1, 0, str, NULL); + break; + } + } +} + +/* + * Initialize event logging + */ +void +openlog(const char *name, int flags, ...) { + /* Get a handle to the Application event log */ + hAppLog = RegisterEventSource(NULL, name); +} + +/* + * Close the Handle to the application Event Log + * We don't care whether or not we succeeded so ignore return values + * In fact if we failed then we would have nowhere to put the message + */ +void +closelog() { + DeregisterEventSource(hAppLog); +} + +#endif diff --git a/subst/syslog.h b/subst/syslog.h new file mode 100644 index 00000000..0b87ceb4 --- /dev/null +++ b/subst/syslog.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: syslog.h,v 1.5 2005/06/02 04:48:25 zion Exp $ */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef HAVE_SYSLOG_H + +#ifndef _SYSLOG_H +#define _SYSLOG_H + +#include + +/* Constant definitions for openlog() */ +#define LOG_PID 1 +#define LOG_CONS 2 +/* NT event log does not support facility level */ +#define LOG_KERN 0 +#define LOG_USER 0 +#define LOG_MAIL 0 +#define LOG_DAEMON 0 +#define LOG_AUTH 0 +#define LOG_SYSLOG 0 +#define LOG_LPR 0 +#define LOG_LOCAL0 0 +#define LOG_LOCAL1 0 +#define LOG_LOCAL2 0 +#define LOG_LOCAL3 0 +#define LOG_LOCAL4 0 +#define LOG_LOCAL5 0 +#define LOG_LOCAL6 0 +#define LOG_LOCAL7 0 + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but signification condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +void +syslog(int level, const char *fmt, ...); + +void +openlog(const char *, int, ...); + +void +closelog(void); + +#endif + +#endif diff --git a/sx/.cvsignore b/sx/.cvsignore new file mode 100644 index 00000000..6e5ca7ed --- /dev/null +++ b/sx/.cvsignore @@ -0,0 +1,6 @@ +Makefile +Makefile.in +.deps +.libs +*.lo +*.la diff --git a/sx/Makefile.am b/sx/Makefile.am new file mode 100644 index 00000000..8ee48ca7 --- /dev/null +++ b/sx/Makefile.am @@ -0,0 +1,5 @@ +noinst_LTLIBRARIES = libsx.la + +noinst_HEADERS = sasl.h ssl.h sx.h +libsx_la_SOURCES = callback.c chain.c client.c env.c error.c io.c sasl.c server.c sasl.c ssl.c sx.c +libsx_la_LIBADD = @LDFLAGS@ diff --git a/sx/callback.c b/sx/callback.c new file mode 100644 index 00000000..cb078016 --- /dev/null +++ b/sx/callback.c @@ -0,0 +1,156 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sx.h" + +/** primary expat callbacks */ +void _sx_element_start(void *arg, const char *name, const char **atts) { + sx_t s = (sx_t) arg; + char buf[1024]; + char *uri, *elem, *prefix; + const char **attr; + int ns; + + if(s->fail) return; + + /* starting a new nad */ + if(s->nad == NULL) + s->nad = nad_new(s->nad_cache); + + /* make a copy */ + strncpy(buf, name, 1024); + buf[1023] = '\0'; + + /* expat gives us: + prefixed namespaced elem: uri|elem|prefix + default namespaced elem: uri|elem + un-namespaced elem: elem + */ + + /* extract all the bits */ + uri = buf; + elem = strchr(uri, '|'); + if(elem != NULL) { + *elem = '\0'; + elem++; + prefix = strchr(elem, '|'); + if(prefix != NULL) { + *prefix = '\0'; + prefix++; + } + ns = nad_add_namespace(s->nad, uri, prefix); + } else { + /* un-namespaced, just take it as-is */ + uri = NULL; + elem = buf; + prefix = NULL; + ns = -1; + } + + /* add it */ + nad_append_elem(s->nad, ns, elem, s->depth - 1); + + /* now the attributes, one at a time */ + attr = atts; + while(attr[0] != NULL) { + + /* make a copy */ + strncpy(buf, attr[0], 1024); + buf[1023] = '\0'; + + /* extract all the bits */ + uri = buf; + elem = strchr(uri, '|'); + if(elem != NULL) { + *elem = '\0'; + elem++; + prefix = strchr(elem, '|'); + if(prefix != NULL) { + *prefix = '\0'; + prefix++; + } + ns = nad_add_namespace(s->nad, uri, prefix); + } else { + /* un-namespaced, just take it as-is */ + uri = NULL; + elem = buf; + prefix = NULL; + ns = -1; + } + + /* add it */ + nad_append_attr(s->nad, ns, elem, (char *) attr[1]); + + attr += 2; + } + + s->depth++; +} + +void _sx_element_end(void *arg, const char *name) { + sx_t s = (sx_t) arg; + + if(s->fail) return; + + s->depth--; + + if(s->depth == 1) { + /* completed nad, save it for later processing */ + jqueue_push(s->rnadq, s->nad, 0); + s->nad = NULL; + } + + /* close received */ + else if(s->depth == 0) + s->depth = -1; +} + +void _sx_cdata(void *arg, const char *str, int len) { + sx_t s = (sx_t) arg; + + if(s->fail) return; + + /* no nad? no cdata */ + if(s->nad == NULL) + return; + + /* go */ + nad_append_cdata(s->nad, (char *) str, len, s->depth - 1); +} + +void _sx_namespace_start(void *arg, const char *prefix, const char *uri) { + sx_t s = (sx_t) arg; + int ns; + + if(s->fail) return; + + /* some versions of MSXML send xmlns='' occassionaally. it seems safe to ignore it */ + if(uri == NULL) return; + + /* starting a new nad */ + if(s->nad == NULL) + s->nad = nad_new(s->nad_cache); + + ns = nad_add_namespace(s->nad, (char *) uri, (char *) prefix); + + /* Always set the namespace (to catch cases where nad_add_namespace doesn't add it) */ + s->nad->scope = ns; +} + diff --git a/sx/chain.c b/sx/chain.c new file mode 100644 index 00000000..113a65ce --- /dev/null +++ b/sx/chain.c @@ -0,0 +1,127 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* manage and run the io and nad chains */ + +#include "sx.h" + +void _sx_chain_io_plugin(sx_t s, sx_plugin_t p) { + _sx_chain_t cn, tail; + + _sx_debug(ZONE, "adding io plugin"); + + cn = (_sx_chain_t) malloc(sizeof(struct _sx_chain_st)); + cn->p = p; + + if(s->wio == NULL) { + s->wio = cn; + cn->wnext = NULL; + } else { + cn->wnext = s->wio; + s->wio = cn; + } + + if(s->rio == NULL) + s->rio = cn; + else { + for(tail = s->rio; tail->rnext != NULL; tail = tail->rnext); + tail->rnext = cn; + } + cn->rnext = NULL; +} + +void _sx_chain_nad_plugin(sx_t s, sx_plugin_t p) { + _sx_chain_t cn, tail; + + _sx_debug(ZONE, "adding nad plugin"); + + cn = (_sx_chain_t) malloc(sizeof(struct _sx_chain_st)); + cn->p = p; + + if(s->wnad == NULL) { + s->wnad = cn; + cn->wnext = NULL; + } else { + cn->wnext = s->wnad; + s->wnad = cn; + } + + if(s->rnad == NULL) + s->rnad = cn; + else { + for(tail = s->rnad; tail->rnext != NULL; tail = tail->rnext); + tail->rnext = cn; + } + cn->rnext = NULL; +} + +int _sx_chain_io_write(sx_t s, sx_buf_t buf) { + _sx_chain_t scan; + int ret = 1; + + _sx_debug(ZONE, "calling io write chain"); + + for(scan = s->wio; scan != NULL; scan = scan->wnext) + if(scan->p->wio != NULL) + if((ret = (scan->p->wio)(s, scan->p, buf)) <= 0) + return ret; + + return ret; +} + +int _sx_chain_io_read(sx_t s, sx_buf_t buf) { + _sx_chain_t scan; + int ret = 1; + + _sx_debug(ZONE, "calling io read chain"); + + for(scan = s->rio; scan != NULL; scan = scan->rnext) + if(scan->p->rio != NULL) + if((ret = (scan->p->rio)(s, scan->p, buf)) <= 0) + return ret; + + return ret; +} + +int _sx_chain_nad_write(sx_t s, nad_t nad, int elem) { + _sx_chain_t scan; + + _sx_debug(ZONE, "calling nad write chain"); + + for(scan = s->wnad; scan != NULL; scan = scan->wnext) + if(scan->p->wnad != NULL) + if((scan->p->wnad)(s, scan->p, nad, elem) == 0) + return 0; + + return 1; +} + +int _sx_chain_nad_read(sx_t s, nad_t nad) { + _sx_chain_t scan; + + _sx_debug(ZONE, "calling nad read chain"); + + for(scan = s->rnad; scan != NULL; scan = scan->rnext) + if(scan->p->rnad != NULL) + if((scan->p->rnad)(s, scan->p, nad) == 0) + return 0; + + return 1; +} diff --git a/sx/client.c b/sx/client.c new file mode 100644 index 00000000..d65f8916 --- /dev/null +++ b/sx/client.c @@ -0,0 +1,177 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sx.h" + +static void _sx_client_element_start(void *arg, const char *name, const char **atts) { + sx_t s = (sx_t) arg; + int tflag = 0, fflag = 0, vflag = 0, iflag = 0, i; + const char **attr; + sx_error_t sxe; + + if(s->fail) return; + + /* check element and namespace */ + i = strlen(uri_STREAMS) + 7; + if(strlen(name) < i || strncmp(name, uri_STREAMS "|stream", i) != 0 || (name[i] != '\0' && name[i] != '|')) { + /* throw an error */ + _sx_gen_error(sxe, SX_ERR_STREAM, "Stream error", "Expected stream start"); + _sx_event(s, event_ERROR, (void *) &sxe); + _sx_error(s, stream_err_BAD_FORMAT, NULL); + s->fail = 1; + return; + } + + /* pull interesting things out of the header */ + attr = atts; + while(attr[0] != NULL) { + if(!tflag && strcmp(attr[0], "to") == 0) { + s->res_to = strdup(attr[1]); + tflag = 1; + } + + if(!fflag && strcmp(attr[0], "from") == 0) { + s->res_from = strdup(attr[1]); + fflag = 1; + } + + if(!vflag && strcmp(attr[0], "version") == 0) { + s->res_version = strdup(attr[1]); + vflag = 1; + } + + if(!iflag && strcmp(attr[0], "id") == 0) { + s->id = strdup(attr[1]); + iflag = 1; + } + + attr += 2; + } + + s->depth++; + + _sx_debug(ZONE, "stream response: to %s from %s version %s id %s", s->res_to, s->res_from, s->res_version, s->id); + + /* we're alive */ + XML_SetElementHandler(s->expat, (void *) _sx_element_start, (void *) _sx_element_end); + XML_SetCharacterDataHandler(s->expat, (void *) _sx_cdata); + XML_SetStartNamespaceDeclHandler(s->expat, (void *) _sx_namespace_start); + + /* get the plugins to setup */ + if(s->env != NULL) + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->stream != NULL) + (s->env->plugins[i]->stream)(s, s->env->plugins[i]); + + /* bump us to stream if a plugin didn't do it already */ + if(s->state < state_STREAM) { + _sx_state(s, state_STREAM); + _sx_event(s, event_STREAM, NULL); + } +} + +static void _sx_client_element_end(void *arg, const char *name) { + sx_t s = (sx_t) arg; + + if(s->fail) return; + + s->depth--; +} + +static void _sx_client_notify_header(sx_t s, void *arg) { + /* expat callbacks */ + XML_SetElementHandler(s->expat, (void *) _sx_client_element_start, (void *) _sx_client_element_end); + + /* state change */ + _sx_state(s, state_STREAM_SENT); + + _sx_debug(ZONE, "stream header sent, waiting for reply"); + + /* waiting for a response */ + s->want_read = 1; +} + +void sx_client_init(sx_t s, unsigned int flags, char *ns, char *to, char *from, char *version) { + sx_buf_t buf; + char *c; + int i, len; + + assert((int) s); + + /* can't do anything if we're alive already */ + if(s->state != state_NONE) + return; + + _sx_debug(ZONE, "doing client init for sx %d", s->tag); + + s->type = type_CLIENT; + s->flags = flags; + + if(ns != NULL) s->ns = strdup(ns); + if(to != NULL) s->req_to = strdup(to); + if(from != NULL) s->req_from = strdup(from); + if(version != NULL) s->req_version = strdup(version); + + /* plugin */ + if(s->env != NULL) + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->client != NULL) + (s->env->plugins[i]->client)(s, s->env->plugins[i]); + + _sx_debug(ZONE, "stream request: ns %s to %s from %s version %s", ns, to, from, version); + + /* build the stream start */ + len = strlen(uri_STREAMS) + 52; + + if(ns != NULL) len += 9 + strlen(ns); + if(to != NULL) len += 6 + strlen(to); + if(from != NULL) len += 8 + strlen(from); + if(version != NULL) len += 11 + strlen(version); + + buf = _sx_buffer_new(NULL, len+1, _sx_client_notify_header, NULL); + c = buf->data; + strcpy(c, ""); + + assert(buf->len == strlen(buf->data)+1); + buf->len --; + + /* plugins can mess with the header too */ + if(s->env != NULL) + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->header != NULL) + (s->env->plugins[i]->header)(s, s->env->plugins[i], buf); + + _sx_debug(ZONE, "prepared stream header: %.*s", buf->len, buf->data); + + /* off it goes */ + jqueue_push(s->wbufq, buf, 0); + + /* we have stuff to write */ + s->want_write = 1; + _sx_event(s, event_WANT_WRITE, NULL); +} + diff --git a/sx/env.c b/sx/env.c new file mode 100644 index 00000000..ecc05abc --- /dev/null +++ b/sx/env.c @@ -0,0 +1,80 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sx.h" + +sx_env_t sx_env_new(void) { + sx_env_t env; + + env = (sx_env_t) malloc(sizeof(struct _sx_env_st)); + memset(env, 0, sizeof(struct _sx_env_st)); + + return env; +} + +void sx_env_free(sx_env_t env) { + int i; + + assert((int) env); + + /* !!! usage counts */ + + for(i = 0; i < env->nplugins; i++) { + if(env->plugins[i]->unload != NULL) + (env->plugins[i]->unload)(env->plugins[i]); + free(env->plugins[i]); + } + + free(env->plugins); + free(env); +} + +sx_plugin_t sx_env_plugin(sx_env_t env, sx_plugin_init_t init, ...) { + sx_plugin_t p; + int ret; + va_list args; + + assert((int) env); + assert((int) init); + + va_start(args, init); + + p = (sx_plugin_t) malloc(sizeof(struct _sx_plugin_st)); + memset(p, 0, sizeof(struct _sx_plugin_st)); + + p->env = env; + p->index = env->nplugins; + + ret = (init)(env, p, args); + va_end(args); + + if(ret != 0) { + free(p); + return NULL; + } + + env->plugins = (sx_plugin_t *) realloc(env->plugins, sizeof(sx_plugin_t) * (env->nplugins + 1)); + env->plugins[env->nplugins] = p; + env->nplugins++; + + _sx_debug(ZONE, "plugin initialised (index %d)", p->index); + + return p; +} diff --git a/sx/error.c b/sx/error.c new file mode 100644 index 00000000..1326ab25 --- /dev/null +++ b/sx/error.c @@ -0,0 +1,96 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sx.h" + +/** if you change these, reflect your changes in the defines in sx.h */ +static char *_stream_errors[] = { + "bad-format", + "bad-namespace-prefix", + "conflict", + "connection-timeout", + "host-gone", + "host-unknown", + "improper-addressing", + "internal-server-error", + "invalid-from", + "invalid-id", + "invalid-namespace", + "invalid-xml", + "not-authorized", + "policy-violation", + "remote-connection-failed", + "restricted-xml", + "resource-constraint", + "see-other-host", + "system-shutdown", + "undefined-condition", + "unsupported-encoding", + "unsupported-stanza-type", + "unsupported-version", + "xml-not-well-formed", + NULL +}; + +/** send an error */ +void _sx_error(sx_t s, int err, char *text) { + int len = 0; + sx_buf_t buf; + + /* build the string */ + if(s->state < state_STREAM) len = strlen(uri_STREAMS) + 61; + len += strlen(uri_STREAMS) + strlen(uri_STREAM_ERR) + strlen(_stream_errors[err]) + 58; + if(text != NULL) len += strlen(uri_STREAM_ERR) + strlen(text) + 22; + + buf = _sx_buffer_new(NULL, len, NULL, NULL); + len = 0; + + if(s->state < state_STREAM) + len = sprintf(buf->data, ""); + + if(text == NULL) + len += sprintf(&(buf->data[len]), "<%s xmlns='" uri_STREAM_ERR "'/>", _stream_errors[err]); + else + len += sprintf(&(buf->data[len]), "<%s xmlns='" uri_STREAM_ERR "'/>%s", _stream_errors[err], text); + + if(s->state < state_STREAM) + len += sprintf(&(buf->data[len]), ""); + + buf->len--; + assert(len == buf->len); + + _sx_debug(ZONE, "prepared error: %.*s", buf->len, buf->data); + + /* go */ + jqueue_push(s->wbufq, buf, 0); + + /* stuff to write */ + s->want_write = 1; +} + +void sx_error(sx_t s, int err, char *text) { + assert(s != NULL); + assert(err >= 0 && err < stream_err_LAST); + + _sx_error(s, err, text); + + _sx_event(s, event_WANT_WRITE, NULL); +} + diff --git a/sx/io.c b/sx/io.c new file mode 100644 index 00000000..fa07abbd --- /dev/null +++ b/sx/io.c @@ -0,0 +1,463 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sx.h" + +/** handler for read data */ +void _sx_process_read(sx_t s, sx_buf_t buf) { + sx_error_t sxe; + nad_t nad; + char *errstring; + int i; + int ns, elem; + + /* Note that buf->len can validly be 0 here, if we got data from + the socket but the plugin didn't return anything to us (e.g. a + SSL packet was split across a tcp segment boundary) */ + + /* parse it */ + if(XML_Parse(s->expat, buf->data, buf->len, 0) == 0) { + /* only report error we haven't already */ + if(!s->fail) { + /* parse error */ + errstring = (char *) XML_ErrorString(XML_GetErrorCode(s->expat)); + + _sx_gen_error(sxe, SX_ERR_XML_PARSE, "XML parse error", errstring); + _sx_event(s, event_ERROR, (void *) &sxe); + + _sx_error(s, stream_err_XML_NOT_WELL_FORMED, errstring); + _sx_close(s); + + _sx_buffer_free(buf); + + return; + } + + /* !!! is this the right thing to do? we should probably set + * s->fail and let the code further down handle it. */ + _sx_buffer_free(buf); + + return; + } + + /* done with the buffer */ + _sx_buffer_free(buf); + + /* process completed nads */ + if(s->state >= state_STREAM) + while((nad = jqueue_pull(s->rnadq)) != NULL) { + int plugin_error; +#ifdef SX_DEBUG + char *out; int len; + nad_print(nad, 0, &out, &len); + _sx_debug(ZONE, "completed nad: %.*s", len, out); +#endif + + /* check for errors */ + if(NAD_ENS(nad, 0) >= 0 && NAD_NURI_L(nad, NAD_ENS(nad, 0)) == strlen(uri_STREAMS) && strncmp(NAD_NURI(nad, NAD_ENS(nad, 0)), uri_STREAMS, strlen(uri_STREAMS)) == 0 && NAD_ENAME_L(nad, 0) == 5 && strncmp(NAD_ENAME(nad, 0), "error", 5) == 0) { + + errstring = NULL; + + /* get text error description if available - XMPP 4.7.2 */ + if((ns = nad_find_scoped_namespace(nad, uri_STREAM_ERR, NULL)) >= 0) + if((elem = nad_find_elem(nad, 0, ns, "text", 1)) >= 0) + if(NAD_CDATA_L(nad, elem) > 0) { + errstring = (char *) malloc(sizeof(char) * (NAD_CDATA_L(nad, elem) + 1)); + sprintf(errstring, "%.*s", NAD_CDATA_L(nad, elem), NAD_CDATA(nad, elem)); + } + + /* if not available, look for legacy error text as in description */ + if (errstring == NULL && NAD_CDATA_L(nad, 0) > 0) { + errstring = (char *) malloc(sizeof(char) * (NAD_CDATA_L(nad, 0) + 1)); + sprintf(errstring, "%.*s", NAD_CDATA_L(nad, 0), NAD_CDATA(nad, 0)); + } + + if(s->state < state_CLOSING) { + _sx_gen_error(sxe, SX_ERR_STREAM, "Stream error", errstring); + _sx_event(s, event_ERROR, (void *) &sxe); + _sx_state(s, state_CLOSING); + } + + if(errstring != NULL) free(errstring); + + nad_free(nad); + + break; + } + + /* run it by the plugins */ + if(_sx_chain_nad_read(s, nad) == 0) + return; + + /* now let the plugins process the completed nad */ + plugin_error = 0; + if(s->env != NULL) + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->process != NULL) { + int plugin_ret; + plugin_ret = (s->env->plugins[i]->process)(s, s->env->plugins[i], nad); + if(plugin_ret == 0) { + plugin_error ++; + break; + } + } + + /* hand it to the app */ + if ((plugin_error == 0) && (s->state < state_CLOSING)) + _sx_event(s, event_PACKET, (void *) nad); + } + + /* something went wrong, bail */ + if(s->fail) { + _sx_close(s); + + return; + } + + /* stream was closed */ + if(s->depth < 0 && s->state < state_CLOSING) { + /* close the stream if necessary */ + if(s->state >= state_STREAM_SENT) { + jqueue_push(s->wbufq, _sx_buffer_new("", 16, NULL, NULL), 0); + s->want_write = 1; + } + + _sx_state(s, state_CLOSING); + + return; + } +} + +/** we can read */ +int sx_can_read(sx_t s) { + sx_buf_t in, out; + int read, ret; + + assert((int) s); + + /* do we care? */ + if(!s->want_read && s->state < state_CLOSING) + return 0; /* no more thanks */ + + _sx_debug(ZONE, "%d ready for reading", s->tag); + + /* new buffer */ + in = _sx_buffer_new(NULL, 1024, NULL, NULL); + + /* get them to read stuff */ + read = _sx_event(s, event_READ, (void *) in); + + /* bail if something went wrong */ + if(read < 0) { + _sx_buffer_free(in); + s->want_read = 0; + s->want_write = 0; + return 0; + } + + /* EOF if we got a 0-byte read from the socket */ + if(read == 0) + /* they went away */ + _sx_state(s, state_CLOSING); + + else { + _sx_debug(ZONE, "passed %d read bytes", in->len); + + /* make a copy for processing */ + out = _sx_buffer_new(in->data, in->len, in->notify, in->notify_arg); + + /* run it by the plugins */ + ret = _sx_chain_io_read(s, out); + if(ret <= 0) { + if(ret < 0) { + /* permanent failure, its all over */ + /* !!! shut down */ + s->want_read = s->want_write = 0; + } + + _sx_buffer_free(in); + _sx_buffer_free(out); + + /* done */ + if(s->want_write) _sx_event(s, event_WANT_WRITE, NULL); + return s->want_read; + } + + _sx_buffer_free(in); + + _sx_debug(ZONE, "decoded read data (%d bytes): %.*s", out->len, out->len, out->data); + + /* into the parser with you */ + _sx_process_read(s, out); + } + + /* if we've written everything, and we're closed, then inform the app it can kill us */ + if(s->want_write == 0 && s->state == state_CLOSING) { + _sx_state(s, state_CLOSED); + _sx_event(s, event_CLOSED, NULL); + return 0; + } + + if(s->state == state_CLOSED) + return 0; + + if(s->want_write) _sx_event(s, event_WANT_WRITE, NULL); + return s->want_read; +} + +/** we can write */ +static int _sx_get_pending_write(sx_t s) { + sx_buf_t in, out; + int ret; + + assert(s != NULL); + + if (s->wbufpending != NULL) { + /* there's already a pending buffer ready to write */ + return 0; + } + + /* get the first buffer off the queue */ + in = jqueue_pull(s->wbufq); + if(in == NULL) { + /* if there was a write event, and something is interested, + we still have to tell the plugins */ + in = _sx_buffer_new(NULL, 0, NULL, NULL); + } + + /* if there's more to write, we want to make sure we get it */ + s->want_write = jqueue_size(s->wbufq); + + /* make a copy for processing */ + out = _sx_buffer_new(in->data, in->len, in->notify, in->notify_arg); + + _sx_debug(ZONE, "encoding %d bytes for writing: %.*s", in->len, in->len, in->data); + + /* run it by the plugins */ + ret = _sx_chain_io_write(s, out); + if(ret <= 0) { + /* TODO/!!!: Are we leaking the 'out' buffer here? How about the 'in' buffer? */ + if(ret == -1) { + /* temporary failure, push it back on the queue */ + jqueue_push(s->wbufq, in, (s->wbufq->front != NULL) ? s->wbufq->front->priority : 0); + s->want_write = 1; + } else if(ret == -2) { + /* permanent failure, its all over */ + /* !!! shut down */ + s->want_read = s->want_write = 0; + return -1; + } + + /* done */ + return 0; + } + + _sx_buffer_free(in); + + if (out->len == 0) + /* if there's nothing to write, then we're done */ + _sx_buffer_free(out); + else + s->wbufpending = out; + + return 0; +} + +int sx_can_write(sx_t s) { + sx_buf_t out; + int ret, written; + + assert((int) s); + + /* do we care? */ + if(!s->want_write && s->state < state_CLOSING) + return 0; /* no more thanks */ + + _sx_debug(ZONE, "%d ready for writing", s->tag); + + ret = _sx_get_pending_write(s); + if (ret < 0) { + /* fatal error */ + /* !!! shut down */ + return 0; + } + + /* if there's nothing to write, then we're done */ + if(s->wbufpending == NULL) { + if(s->want_read) _sx_event(s, event_WANT_READ, NULL); + return s->want_write; + } + + out = s->wbufpending; + s->wbufpending = NULL; + + /* get the callback to do the write */ + _sx_debug(ZONE, "handing app %d bytes to write", out->len); + written = _sx_event(s, event_WRITE, (void *) out); + + if(written < 0) { + /* bail if something went wrong */ + _sx_buffer_free(out); + s->want_read = 0; + s->want_write = 0; + return 0; + } else if(written < out->len) { + /* if not fully written, this buffer is still pending */ + out->len -= written; + out->data += written; + s->wbufpending = out; + s->want_write ++; + } else { + /* notify */ + if(out->notify != NULL) + (out->notify)(s, out->notify_arg); + + /* done with this */ + _sx_buffer_free(out); + } + + /* if we've written everything, and we're closed, then inform the app it can kill us */ + if(s->want_write == 0 && s->state == state_CLOSING) { + _sx_state(s, state_CLOSED); + _sx_event(s, event_CLOSED, NULL); + return 0; + } + + if(s->state == state_CLOSED) + return 0; + + if(s->want_read) _sx_event(s, event_WANT_READ, NULL); + return s->want_write; +} + +/** send a new nad out */ +int _sx_nad_write(sx_t s, nad_t nad, int elem) { + char *out; + int len; + + /* silently drop it if we're closing or closed */ + if(s->state >= state_CLOSING) { + log_debug(ZONE, "stream closed, dropping outgoing packet"); + nad_free(nad); + return 1; + } + + /* run it through the plugins */ + if(_sx_chain_nad_write(s, nad, elem) == 0) + return 1; + + /* serialise it */ + nad_print(nad, elem, &out, &len); + + _sx_debug(ZONE, "queueing for write: %.*s", len, out); + + /* ready to go */ + jqueue_push(s->wbufq, _sx_buffer_new(out, len, NULL, NULL), 0); + + nad_free(nad); + + /* things to write */ + s->want_write = 1; + + return 0; +} + +/** app version */ +void sx_nad_write_elem(sx_t s, nad_t nad, int elem) { + assert((int) s); + assert((int) nad); + + if(_sx_nad_write(s, nad, elem) == 1) + return; + + /* things to write */ + s->want_write = 1; + _sx_event(s, event_WANT_WRITE, NULL); + + if(s->want_read) _sx_event(s, event_WANT_READ, NULL); +} + +/** send raw data out */ +int _sx_raw_write(sx_t s, char *buf, int len) { + /* siltently drop it if we're closing or closed */ + if(s->state >= state_CLOSING) { + log_debug(ZONE, "stream closed, dropping outgoing raw data"); + return 1; + } + + _sx_debug(ZONE, "queuing for write: %.*s", len, buf); + + /* ready to go */ + jqueue_push(s->wbufq, _sx_buffer_new(buf, len, NULL, NULL), 0); + + /* things to write */ + s->want_write = 1; + + return 0; +} + +/** app version */ +void sx_raw_write(sx_t s, char *buf, int len) { + assert((int) s); + assert((int) buf); + assert(len); + + if(_sx_raw_write(s, buf, len) == 1) + return; + + /* things to write */ + s->want_write = 1; + _sx_event(s, event_WANT_WRITE, NULL); + + if(s->want_read) _sx_event(s, event_WANT_READ, NULL); +} + +/** close a stream */ +void _sx_close(sx_t s) { + /* close the stream if necessary */ + if(s->state >= state_STREAM_SENT) { + jqueue_push(s->wbufq, _sx_buffer_new("
", 16, NULL, NULL), 0); + s->want_write = 1; + } + + _sx_state(s, state_CLOSING); +} + +void sx_close(sx_t s) { + assert((int) s); + + if(s->state >= state_CLOSING) + return; + + if(s->state >= state_STREAM_SENT && s->state < state_CLOSING) { + _sx_close(s); + _sx_event(s, event_WANT_WRITE, NULL); + } else { + _sx_state(s, state_CLOSED); + _sx_event(s, event_CLOSED, NULL); + } +} + +void sx_kill(sx_t s) { + assert((int) s); + + _sx_state(s, state_CLOSED); + _sx_event(s, event_CLOSED, NULL); +} diff --git a/sx/sasl.c b/sx/sasl.c new file mode 100644 index 00000000..e9a968e2 --- /dev/null +++ b/sx/sasl.c @@ -0,0 +1,1092 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* SASL authentication handler */ + +#include "sx.h" +#include "ssl.h" +#include "sasl.h" +/* Gack - need this otherwise SASL's MD5 definitions conflict with OpenSSLs */ +#define MD5_H +#include + +/* RFC 3290 defines a number of failure messages */ +#define _sasl_err_ABORTED "aborted" +#define _sasl_err_INCORRECT_ENCODING "incorrect-encoding" +#define _sasl_err_INVALID_AUTHZID "invalid-authzid" +#define _sasl_err_INVALID_MECHANISM "invalid-mechanism" +#define _sasl_err_MECH_TOO_WEAK "mechanism-too-weak" +#define _sasl_err_NOT_AUTHORIZED "not-authorized" +#define _sasl_err_TEMPORARY_FAILURE "temporary-auth-failure" + +/* Forward definitions */ +static void _sx_sasl_free(sx_t, sx_plugin_t); + +/* Support auxprop so that we can use the standard Jabber authreg plugins + * with SASL mechanisms requiring passwords + */ +static void _sx_auxprop_lookup(void *glob_context, + sasl_server_params_t *sparams, + unsigned flags, + const char *user, + unsigned ulen) { + char *userid = NULL; + char *realm = NULL; + int ret; + const struct propval *to_fetch, *current; + char *user_buf = NULL; + char *value; + _sx_sasl_t ctx = (_sx_sasl_t) glob_context; + struct sx_sasl_creds_st creds = {NULL, NULL, NULL, NULL}; + + if (!sparams || !user) + return; + + /* It would appear that there's no guarantee that 'user' is NULL + * terminated, so we'd better terminate it ... + */ + + user_buf = sparams->utils->malloc(ulen + 1); + if (!user_buf) + goto done; + + memcpy(user_buf, user, ulen); + user_buf[ulen] = '\0'; + + /* Parse the supplied username, splitting it into user and realm + * components. I suspect that 'parseuser' isn't actually part of the + * exported API, so maybe we should reimplement this. */ + + ret = _plug_parseuser(sparams->utils, &userid, &realm, + sparams->user_realm?sparams->user_realm: + sparams->serverFQDN, + user_buf); + if (ret != SASL_OK) + goto done; + + + /* At present, we only handle fetching the user's password */ + to_fetch = sparams->utils->prop_get(sparams->propctx); + if (!to_fetch) + goto done; + for (current = to_fetch; current->name; current++) { + if (strcmp(current->name, SASL_AUX_PASSWORD) == 0) { + /* If we've already got a value, see if we can override it */ + if (current->values) { + if (flags & SASL_AUXPROP_OVERRIDE) + sparams->utils->prop_erase(sparams->propctx, current->name); + else + continue; + } + + /* Do the lookup, returning the results into value and value_len */ + if (strcmp(SASL_AUX_PASSWORD_PROP, current->name)) { + creds.authnid = userid; + creds.realm = realm; + if ((ctx->cb)(sx_sasl_cb_GET_PASS, &creds, (void **)&value, + NULL, ctx->cbarg) == sx_sasl_ret_OK) { + sparams->utils->prop_set(sparams->propctx, current->name, + value, strlen(value)); + } + } + } + } + done: + if (userid) sparams->utils->free(userid); + if (realm) sparams->utils->free(realm); + if (user_buf) sparams->utils->free(user_buf); +} + +static sasl_auxprop_plug_t _sx_auxprop_plugin = + {0, 0, NULL, NULL, _sx_auxprop_lookup, "jabberdsx", NULL}; + +static int +sx_auxprop_init(const sasl_utils_t *utils, int max_version, int *out_version, + sasl_auxprop_plug_t **plug, const char *plugname) { + + if (!out_version || !plug) + return SASL_BADPARAM; + if (max_version < SASL_AUXPROP_PLUG_VERSION ) + return SASL_BADVERS; + + *out_version = SASL_AUXPROP_PLUG_VERSION; + *plug = &_sx_auxprop_plugin; + + return SASL_OK; +} + +/* This handles those authreg plugins which won't provide plaintext access + * to the user's password. Note that there are very few mechanisms which + * call the verify function, rather than asking for the password + */ +static int _sx_sasl_checkpass(sasl_conn_t *conn, void *ctx, const char *user, const char *pass, unsigned passlen, struct propctx *propctx) { + _sx_sasl_data_t sd = (_sx_sasl_data_t)ctx; + struct sx_sasl_creds_st creds = {NULL, NULL, NULL, NULL}; + + creds.authnid = user; + creds.pass = pass; + + if (sd->ctx->cb(sx_sasl_cb_CHECK_PASS, &creds, NULL, sd->stream, sd->ctx->cbarg)==sx_sasl_ret_OK) { + return SASL_OK; + } else { + return SASL_BADAUTH; + } +} + +/* Canonicalize the username. Normally this does nothing, but if we're + * calling from an anonymous plugin, then we need to generate a JID for + * the user + */ + +static int _sx_sasl_canon_user(sasl_conn_t *conn, void *ctx, const char *user, unsigned ulen, unsigned flags, const char *user_realm, char *out_user, unsigned out_umax, unsigned *out_ulen) { + char *buf; + _sx_sasl_data_t sd = (_sx_sasl_data_t)ctx; + sasl_getprop(conn, SASL_MECHNAME, (const void **) &buf); + if (strcmp(buf, "ANONYMOUS") == 0) { + sd->ctx->cb(sx_sasl_cb_GEN_AUTHZID, NULL, (void **)&buf, sd->stream, sd->ctx->cbarg); + strncpy(out_user, buf, out_umax); + out_user[out_umax]='\0'; + *out_ulen=strlen(out_user); + } else { + memcpy(out_user,user,ulen); + *out_ulen = ulen; + } + return SASL_OK; +} + +/* Need to make sure that + * *) The authnid is permitted to become the given authzid + * *) The authnid is included in the given authreg systems DB + */ +static int _sx_sasl_proxy_policy(sasl_conn_t *conn, void *ctx, const char *requested_user, int rlen, const char *auth_identity, int alen, const char *realm, int urlen, struct propctx *propctx) { + _sx_sasl_data_t sd = (_sx_sasl_data_t) ctx; + struct sx_sasl_creds_st creds = {NULL, NULL, NULL, NULL}; + char *buf; + + sasl_getprop(conn, SASL_MECHNAME, (const void **) &buf); + if (strcmp(buf, "ANONYMOUS") == 0) { + /* If they're anonymous, their ID comes from us, so it must be OK! */ + return SASL_OK; + } else { + if (!requested_user || !auth_identity || alen != rlen || + (memcmp(requested_user, auth_identity, rlen) !=0)) { + sasl_seterror(conn, 0, + "Requested identity is not authenticated identity"); + return SASL_BADAUTH; + } + creds.authnid = auth_identity; + creds.realm = realm; + creds.authzid = requested_user; + /* If we start being fancy and allow auth_identity to be different from + * requested_user, then this will need to be changed to permit it! + */ + if ((sd->ctx->cb)(sx_sasl_cb_CHECK_AUTHZID, &creds, NULL, sd->stream, sd->ctx->cbarg)==sx_sasl_ret_OK) + return SASL_OK; + else + return SASL_BADAUTH; + } +} + +static int _sx_sasl_wio(sx_t s, sx_plugin_t p, sx_buf_t buf) { + sasl_conn_t *sasl; + int *x, len, pos, reslen, maxbuf; + char *out, *result; + + sasl = ((_sx_sasl_data_t) s->plugin_data[p->index])->sasl; + + /* if there's no security layer, don't bother */ + sasl_getprop(sasl, SASL_SSF, (const void **) &x); + if(*x == 0) + return 1; + + _sx_debug(ZONE, "doing sasl encode"); + + /* can only encode x bytes at a time */ + sasl_getprop(sasl, SASL_MAXOUTBUF, (const void **) &x); + maxbuf = *x; + + /* encode the output */ + pos = 0; + result = NULL; reslen = 0; + while(pos < buf->len) { + if((buf->len - pos) < maxbuf) + maxbuf = buf->len - pos; + + sasl_encode(sasl, &buf->data[pos], maxbuf, (const char **) &out, &len); + + result = (char *) realloc(result, sizeof(char) * (reslen + len)); + memcpy(&result[reslen], out, len); + reslen += len; + + pos += maxbuf; + } + + /* replace the buffer */ + _sx_buffer_set(buf, result, reslen, result); + + _sx_debug(ZONE, "%d bytes encoded for sasl channel", buf->len); + + return 1; +} + +static int _sx_sasl_rio(sx_t s, sx_plugin_t p, sx_buf_t buf) { + sasl_conn_t *sasl; + int *x, len; + char *out; + + sasl = ((_sx_sasl_data_t) s->plugin_data[p->index])->sasl; + + /* if there's no security layer, don't bother */ + sasl_getprop(sasl, SASL_SSF, (const void **) &x); + if(*x == 0) + return 1; + + _sx_debug(ZONE, "doing sasl decode"); + + /* decode the input */ + sasl_decode(sasl, buf->data, buf->len, (const char **) &out, &len); + + /* replace the buffer */ + _sx_buffer_set(buf, out, len, NULL); + + _sx_debug(ZONE, "%d bytes decoded from sasl channel", len); + + return 1; +} + +/** move the stream to the auth state */ +void _sx_sasl_open(sx_t s, sasl_conn_t *sasl) { + char *method; + char *buf; + int *ssf; + + /* get the method */ + sasl_getprop(sasl, SASL_MECHNAME, (const void **) &buf); + + method = (char *) malloc(sizeof(char) * (strlen(buf) + 17)); + sprintf(method, "SASL/%s", buf); + + /* get the ssf */ + if(s->ssf == 0) { + sasl_getprop(sasl, SASL_SSF, (const void **) &ssf); + s->ssf = *ssf; + } + + /* and the authenticated id */ + sasl_getprop(sasl, SASL_USERNAME, (const void **) &buf); + + /* schwing! */ + sx_auth(s, method, buf); + + free(method); +} + +/** make the stream suthenticated second time round */ +static void _sx_sasl_stream(sx_t s, sx_plugin_t p) { + _sx_sasl_t ctx = (_sx_sasl_t) p->private; + sasl_conn_t *sasl; + _sx_sasl_data_t sd; + int ret, i; + char *realm, *ext_id, *mech; + sasl_security_properties_t sec_props; + + /* First time around, we need to set up our SASL connection, otherwise + * features will fall flat on its face */ + if (s->plugin_data[p->index] == NULL) { + if(s->type == type_SERVER) { + + if(!(s->flags & SX_SASL_OFFER)) { + _sx_debug(ZONE, "application did not request sasl offer, not offering for this conn"); + return; + } + + _sx_debug(ZONE, "setting up sasl for this server conn"); + + /* Initialise our data object */ + sd = (_sx_sasl_data_t) malloc(sizeof(struct _sx_sasl_data_st)); + memset(sd, 0, sizeof(struct _sx_sasl_data_st)); + + /* get the realm */ + if(ctx->cb != NULL) + (ctx->cb)(sx_sasl_cb_GET_REALM, NULL, (void **) &realm, s, ctx->cbarg); + + /* Initialize our callbacks */ + sd->callbacks = calloc(sizeof(sasl_callback_t),4); + + sd->callbacks[0].id = SASL_CB_PROXY_POLICY; + sd->callbacks[0].proc = &_sx_sasl_proxy_policy; + sd->callbacks[0].context = sd; + + sd->callbacks[1].id = SASL_CB_CANON_USER; + sd->callbacks[1].proc = &_sx_sasl_canon_user; + sd->callbacks[1].context = sd; + + sd->callbacks[2].id = SASL_CB_SERVER_USERDB_CHECKPASS; + sd->callbacks[2].proc = &_sx_sasl_checkpass; + sd->callbacks[2].context = sd; + + sd->callbacks[3].id = SASL_CB_LIST_END; + + /* startup */ + ret = sasl_server_new(ctx->appname, NULL, + realm[0] == '\0' ? NULL : realm, + NULL, NULL, sd->callbacks, + ctx->sec_props.security_flags, &sasl); + if(ret != SASL_OK) { + _sx_debug(ZONE, "sasl_server_new failed (%s), not offering sasl for this conn", sasl_errstring(ret, NULL, NULL)); + return; + } + + /* get external data from the ssl plugin */ + ext_id = NULL; + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->magic == SX_SASL_SSL_MAGIC) + ext_id = (char *) s->plugin_data[s->env->plugins[i]->index]; + + /* if we've got some, setup for external auth */ + ret = SASL_OK; + if(ext_id != NULL) { + ret = sasl_setprop(sasl, SASL_AUTH_EXTERNAL, ext_id); + if(ret == SASL_OK) + ret = sasl_setprop(sasl, SASL_SSF_EXTERNAL, &s->ssf); + } + + /* security properties */ + sec_props = ctx->sec_props; + if(s->ssf > 0) + /* if we're already encrypted, then no security layers */ + sec_props.max_ssf = 0; + + if(ret == SASL_OK) + ret = sasl_setprop(sasl, SASL_SEC_PROPS, &sec_props); + + if(ret != SASL_OK) { + _sx_debug(ZONE, "sasl_setprop failed (%s), not offering sasl for this conn", sasl_errstring(ret, NULL, NULL)); + return; + } + + sd->sasl = sasl; + sd->stream = s; + sd->ctx = ctx; + + _sx_debug(ZONE, "sasl context initialised for %d", s->tag); + + s->plugin_data[p->index] = (void *) sd; + + } + + return; + } + + sasl = ((_sx_sasl_data_t) s->plugin_data[p->index])->sasl; + + /* are we auth'd? */ + if (sasl_getprop(sasl, SASL_MECHNAME, (void *) &mech) == SASL_NOTDONE) { + _sx_debug(ZONE, "not auth'd, not advancing to auth'd state yet"); + return; + } + + /* otherwise, its auth time */ + _sx_sasl_open(s, sasl); +} + +static void _sx_sasl_features(sx_t s, sx_plugin_t p, nad_t nad) { + _sx_sasl_data_t sd = (_sx_sasl_data_t) s->plugin_data[p->index]; + int ret, nmechs, ns; + char *mechs, *mech, *c; + + if(s->type != type_SERVER || sd == NULL || sd->sasl == NULL) + return; + + if((ret = sasl_getprop(sd->sasl, SASL_MECHNAME, (void *) &mech)) != SASL_NOTDONE) { + _sx_debug(ZONE, "already auth'd, not offering sasl mechanisms"); + return; + } + + if(!(s->flags & SX_SASL_OFFER)) { + _sx_debug(ZONE, "application didn't ask us to offer sasl, so we won't"); + return; + } + +#ifdef HAVE_SSL + if((s->flags & SX_SSL_STARTTLS_REQUIRE) && s->ssf == 0) { + _sx_debug(ZONE, "ssl not established yet but the app requires it, not offering mechanisms"); + return; + } +#endif + + _sx_debug(ZONE, "offering sasl mechanisms"); + + ret = sasl_listmech(sd->sasl, NULL, "", "|", "", (const char **) &mechs, NULL, &nmechs); + if(ret != SASL_OK) { + _sx_debug(ZONE, "sasl_listmech failed (%s), not offering sasl for this conn", sasl_errstring(ret, NULL, NULL)); + _sx_sasl_free(s,p); + return; + } + + if(nmechs <= 0) { + _sx_debug(ZONE, "sasl_listmech returned no mechanisms, not offering sasl for this conn"); + _sx_sasl_free(s,p); + return; + } + + mech = mechs; + nmechs = 0; + while(mech != NULL) { + c = strchr(mech, '|'); + if(c != NULL) + *c = '\0'; + + if ((sd->ctx->cb)(sx_sasl_cb_CHECK_MECH, mech, NULL, sd->stream, sd->ctx->cbarg)==sx_sasl_ret_OK) { + if (nmechs == 0) { + ns = nad_add_namespace(nad, uri_SASL, NULL); + nad_append_elem(nad, ns, "mechanisms", 1); + } + _sx_debug(ZONE, "offering mechanism: %s", mech); + + nad_append_elem(nad, ns, "mechanism", 2); + nad_append_cdata(nad, mech, strlen(mech), 3); + nmechs++; + } + + if(c == NULL) + mech = NULL; + else + mech = ++c; + } +} + +/** utility: generate a success nad */ +static nad_t _sx_sasl_success(sx_t s) { + nad_t nad; + int ns; + + nad = nad_new(s->nad_cache); + ns = nad_add_namespace(nad, uri_SASL, NULL); + + nad_append_elem(nad, ns, "success", 0); + + return nad; +} + +/** utility: generate a failure nad */ +static nad_t _sx_sasl_failure(sx_t s, const char *err) { + nad_t nad; + int ns; + + nad = nad_new(s->nad_cache); + ns = nad_add_namespace(nad, uri_SASL, NULL); + + nad_append_elem(nad, ns, "failure", 0); + if(err != NULL) + nad_append_elem(nad, ns, err, 1); + + return nad; +} + +/** utility: generate a challenge nad */ +static nad_t _sx_sasl_challenge(sx_t s, char *data, int dlen) { + nad_t nad; + int ns; + + nad = nad_new(s->nad_cache); + ns = nad_add_namespace(nad, uri_SASL, NULL); + + nad_append_elem(nad, ns, "challenge", 0); + if(data != NULL) + nad_append_cdata(nad, data, dlen, 1); + + return nad; +} + +/** utility: generate a response nad */ +static nad_t _sx_sasl_response(sx_t s, char *data, int dlen) { + nad_t nad; + int ns; + + nad = nad_new(s->nad_cache); + ns = nad_add_namespace(nad, uri_SASL, NULL); + + nad_append_elem(nad, ns, "response", 0); + if(data != NULL) + nad_append_cdata(nad, data, dlen, 1); + + return nad; +} + +/** utility: generate an abort nad */ +static nad_t _sx_sasl_abort(sx_t s) { + nad_t nad; + int ns; + + nad = nad_new(s->nad_cache); + ns = nad_add_namespace(nad, uri_SASL, NULL); + + nad_append_elem(nad, ns, "abort", 0); + + return nad; +} + +/** utility: decode incoming handshake data */ +static void _sx_sasl_decode(char *in, int inlen, char **out, int *outlen) { + *outlen = ap_base64decode_len(in, inlen); + *out = (char *) malloc(sizeof(char) * (*outlen + 1)); + ap_base64decode(*out, in, inlen); +} + +/** utility: encode outgoing handshake data */ +static void _sx_sasl_encode(char *in, int inlen, char **out, int *outlen) { + *outlen = ap_base64encode_len(inlen); + *out = (char *) malloc(sizeof(char) * *outlen); + ap_base64encode(*out, in, inlen); + (*outlen)--; +} + +/** auth done, restart the stream */ +static void _sx_sasl_notify_success(sx_t s, void *arg) { + sx_plugin_t p = (sx_plugin_t) arg; + + _sx_chain_io_plugin(s, p); + _sx_debug(ZONE, "auth completed, resetting"); + + _sx_reset(s); + + sx_server_init(s, s->flags); +} + +/** process handshake packets from the client */ +static void _sx_sasl_client_process(sx_t s, sx_plugin_t p, char *mech, char *in, int inlen) { + _sx_sasl_data_t sd = (_sx_sasl_data_t) s->plugin_data[p->index]; + char *buf, *out; + int buflen, outlen, ret; + + if(mech != NULL) { + _sx_debug(ZONE, "auth request from client (mechanism=%s)", mech); + } else { + _sx_debug(ZONE, "response from client"); + } + + /* decode the response */ + _sx_sasl_decode(in, inlen, &buf, &buflen); + + /* process the data */ + if(mech != NULL) + ret = sasl_server_start(sd->sasl, mech, buf, buflen, (const char **) &out, &outlen); + else + ret = sasl_server_step(sd->sasl, buf, buflen, (const char **) &out, &outlen); + + if(buf != NULL) free(buf); + + /* auth completed */ + if(ret == SASL_OK) { + _sx_debug(ZONE, "sasl handshake completed"); + + /* send success */ + _sx_nad_write(s, _sx_sasl_success(s), 0); + + /* set a notify on the success nad buffer */ + ((sx_buf_t) s->wbufq->front->data)->notify = _sx_sasl_notify_success; + ((sx_buf_t) s->wbufq->front->data)->notify_arg = (void *) p; + + return; + } + + /* in progress */ + if(ret == SASL_CONTINUE) { + _sx_debug(ZONE, "sasl handshake in progress (challenge: %.*s)", outlen, out); + + /* encode the challenge */ + _sx_sasl_encode(out, outlen, &buf, &buflen); + + _sx_nad_write(s, _sx_sasl_challenge(s, buf, buflen), 0); + + free(buf); + + return; + } + + /* its over */ + buf = (char *) sasl_errdetail(sd->sasl); + if(buf == NULL) + buf = "[no error message available]"; + + _sx_debug(ZONE, "sasl handshake failed: %s", buf); + + _sx_nad_write(s, _sx_sasl_failure(s, _sasl_err_TEMPORARY_FAILURE), 0); +} + +/** process handshake packets from the server */ +static void _sx_sasl_server_process(sx_t s, sx_plugin_t p, char *in, int inlen) { + _sx_sasl_data_t sd = (_sx_sasl_data_t)s->plugin_data[p->index]; + char *buf, *out; + int buflen, outlen, ret; + const char *err_buf; + + _sx_debug(ZONE, "challenge from client"); + + /* decode the response */ + _sx_sasl_decode(in, inlen, &buf, &buflen); + + /* process the data */ + ret = sasl_client_step(sd->sasl, buf, buflen, NULL, (const char **) &out, &outlen); + if(buf != NULL) free(buf); + + /* in progress */ + if(ret == SASL_OK || ret == SASL_CONTINUE) { + _sx_debug(ZONE, "sasl handshake in progress (response: %.*s)", outlen, out); + + /* encode the response */ + _sx_sasl_encode(out, outlen, &buf, &buflen); + + _sx_nad_write(s, _sx_sasl_response(s, buf, buflen), 0); + + if(buf != NULL) free(buf); + + return; + } + + /* its over */ + err_buf = sasl_errdetail(sd->sasl); + if (err_buf == NULL) + err_buf = "[no error message available]"; + + _sx_debug(ZONE, "sasl handshake aborted: %s", err_buf); + + _sx_nad_write(s, _sx_sasl_abort(s), 0); +} + +/** main nad processor */ +static int _sx_sasl_process(sx_t s, sx_plugin_t p, nad_t nad) { + _sx_sasl_data_t sd = (_sx_sasl_data_t)s->plugin_data[p->index]; + int attr; + char mech[128]; + sx_error_t sxe; + int flags; + char *ns = NULL, *to = NULL, *from = NULL, *version = NULL; + + /* only want sasl packets */ + if(NAD_ENS(nad, 0) < 0 || NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_SASL) || strncmp(NAD_NURI(nad, NAD_ENS(nad, 0)), uri_SASL, strlen(uri_SASL)) != 0) + return 1; + + /* quietly drop it if sasl is disabled, or if not ready */ + if(s->state != state_STREAM || sd == NULL) { + _sx_debug(ZONE, "not correct state for sasl, ignoring"); + nad_free(nad); + return 0; + } + + /* packets from the client */ + if(s->type == type_SERVER) { + if(!(s->flags & SX_SASL_OFFER)) { + _sx_debug(ZONE, "they tried to do sasl, but we never offered it, ignoring"); + nad_free(nad); + return 0; + } + +#ifdef HAVE_SSL + if((s->flags & SX_SSL_STARTTLS_REQUIRE) && s->ssf == 0) { + _sx_debug(ZONE, "they tried to do sasl, but they have to do starttls first, ignoring"); + nad_free(nad); + return 0; + } +#endif + + /* auth */ + if(NAD_ENAME_L(nad, 0) == 4 && strncmp("auth", NAD_ENAME(nad, 0), NAD_ENAME_L(nad, 0)) == 0) { + /* require mechanism */ + if((attr = nad_find_attr(nad, 0, -1, "mechanism", NULL)) < 0) { + _sx_nad_write(s, _sx_sasl_failure(s, _sasl_err_INVALID_MECHANISM), 0); + nad_free(nad); + return 0; + } + + /* extract */ + snprintf(mech, 127, "%.*s", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + + /* go */ + _sx_sasl_client_process(s, p, mech, NAD_CDATA(nad, 0), NAD_CDATA_L(nad, 0)); + + nad_free(nad); + return 0; + } + + /* response */ + else if(NAD_ENAME_L(nad, 0) == 8 && strncmp("response", NAD_ENAME(nad, 0), NAD_ENAME_L(nad, 0)) == 0) { + /* process it */ + _sx_sasl_client_process(s, p, NULL, NAD_CDATA(nad, 0), NAD_CDATA_L(nad, 0)); + + nad_free(nad); + return 0; + } + + /* abort */ + else if(NAD_ENAME_L(nad, 0) == 5 && strncmp("abort", NAD_ENAME(nad, 0), NAD_ENAME_L(nad, 0)) == 0) { + _sx_debug(ZONE, "sasl handshake aborted"); + + _sx_nad_write(s, _sx_sasl_failure(s, _sasl_err_ABORTED), 0); + + nad_free(nad); + return 0; + } + } + + /* packets from the server */ + else if(s->type == type_CLIENT) { + if(sd == NULL) { + _sx_debug(ZONE, "got sasl client packets, but they never started sasl, ignoring"); + nad_free(nad); + return 0; + } + + /* challenge */ + if(NAD_ENAME_L(nad, 0) == 9 && strncmp("challenge", NAD_ENAME(nad, 0), NAD_ENAME_L(nad, 0)) == 0) { + /* process it */ + _sx_sasl_server_process(s, p, NAD_CDATA(nad, 0), NAD_CDATA_L(nad, 0)); + + nad_free(nad); + return 0; + } + + /* success */ + else if(NAD_ENAME_L(nad, 0) == 7 && strncmp("success", NAD_ENAME(nad, 0), NAD_ENAME_L(nad, 0)) == 0) { + _sx_debug(ZONE, "sasl handshake completed, resetting"); + nad_free(nad); + + /* save interesting bits */ + flags = s->flags; + + if(s->ns != NULL) ns = strdup(s->ns); + + if(s->req_to != NULL) to = strdup(s->req_to); + if(s->req_from != NULL) from = strdup(s->req_from); + if(s->req_version != NULL) version = strdup(s->req_version); + + /* setup the encoder */ + _sx_chain_io_plugin(s, p); + + /* reset state */ + _sx_reset(s); + + _sx_debug(ZONE, "restarting stream with sasl layer established"); + + /* second time round */ + sx_client_init(s, flags, ns, to, from, version); + + /* free bits */ + if(ns != NULL) free(ns); + if(to != NULL) free(to); + if(from != NULL) free(from); + if(version != NULL) free(version); + + return 0; + } + + /* failure */ + else if(NAD_ENAME_L(nad, 0) == 7 && strncmp("failure", NAD_ENAME(nad, 0), NAD_ENAME_L(nad, 0)) == 0) { + /* fire the error */ + _sx_gen_error(sxe, SX_ERR_AUTH, "Authentication failed", NULL); + _sx_event(s, event_ERROR, (void *) &sxe); + + /* cleanup */ + _sx_sasl_free(s,p); + + nad_free(nad); + return 0; + } + } + + /* invalid sasl command, quietly drop it */ + _sx_debug(ZONE, "unknown sasl command '%.*s', ignoring", NAD_ENAME_L(nad, 0), NAD_ENAME(nad, 0)); + + nad_free(nad); + return 0; +} + +/** cleanup */ +static void _sx_sasl_free(sx_t s, sx_plugin_t p) { + _sx_sasl_data_t sd = (_sx_sasl_data_t) s->plugin_data[p->index]; + + if(sd == NULL) + return; + + _sx_debug(ZONE, "cleaning up conn state"); + + if(sd->sasl != NULL) sasl_dispose(&sd->sasl); + if(sd->user != NULL) free(sd->user); + if(sd->psecret != NULL) free(sd->psecret); + + free(sd); + + s->plugin_data[p->index] = NULL; +} + +static void _sx_sasl_unload(sx_plugin_t p) { + + if (p->private) + free(p->private); +} + +/** args: appname, flags, callback, cb arg */ +int sx_sasl_init(sx_env_t env, sx_plugin_t p, va_list args) { + char *appname; + int flags; + sx_sasl_callback_t cb; + void *cbarg; + int ret; + _sx_sasl_t ctx; + + _sx_debug(ZONE, "initialising sasl plugin"); + + appname = va_arg(args, char *); + if(appname == NULL) { + _sx_debug(ZONE, "appname was NULL, failing"); + return 1; + } + + flags = va_arg(args, int); + + cb = va_arg(args, sx_sasl_callback_t); + cbarg = va_arg(args, void *); + + /* Set up the auxiliary property plugin, which we use to gave SASL + * mechanism plugins access to our passwords + */ + sasl_auxprop_add_plugin("jabbersx", sx_auxprop_init); + + ctx = (_sx_sasl_t) malloc(sizeof(struct _sx_sasl_st)); + memset(ctx, 0, sizeof(struct _sx_sasl_st)); + + ctx->appname = strdup(appname); + + ctx->sec_props.min_ssf = 0; + ctx->sec_props.max_ssf = -1; /* sasl_ssf_t is typedef'd to unsigned, so -1 gets us the max possible ssf */ + ctx->sec_props.maxbufsize = 1024; + ctx->sec_props.security_flags = flags; + + ctx->cb = cb; + ctx->cbarg = cbarg; + + /* Push the location of our callbacks into the auxprop structure */ + + _sx_auxprop_plugin.glob_context = (void *) ctx; + + ret = sasl_server_init(NULL, appname); + if(ret != SASL_OK) { + _sx_debug(ZONE, "sasl_server_init() failed (%s), disabling", sasl_errstring(ret, NULL, NULL)); + return 1; + } + + _sx_debug(ZONE, "sasl context initialised; appname=%s", appname); + + p->private = (void *) ctx; + + p->unload = _sx_sasl_unload; + p->wio = _sx_sasl_wio; + p->rio = _sx_sasl_rio; + + p->stream = _sx_sasl_stream; + p->features = _sx_sasl_features; + p->process = _sx_sasl_process; + + p->free = _sx_sasl_free; + + return 0; +} + +/* callback functions for client auth */ +static int _sx_sasl_cb_get_simple(void *ctx, int id, const char **result, unsigned *len) +{ + _sx_sasl_data_t sd = (_sx_sasl_data_t) ctx; + + _sx_debug(ZONE, "in _sx_sasl_cb_get_simple (id 0x%x)", id); + + *result = sd->user; + if(len != NULL) + *len = strlen(*result); + + return SASL_OK; +} + +static int _sx_sasl_cb_get_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **psecret) +{ + _sx_sasl_data_t sd = (_sx_sasl_data_t) ctx; + + _sx_debug(ZONE, "in _sx_sasl_cb_get_secret (id 0x%x)", id); + + /* sanity check */ + if(conn == NULL || psecret == NULL || id != SASL_CB_PASS) + return SASL_BADPARAM; + + *psecret = sd->psecret; + + return SASL_OK; +} + +/** kick off the auth handshake */ +int sx_sasl_auth(sx_plugin_t p, sx_t s, char *appname, char *mech, char *user, char *pass) { + _sx_sasl_t ctx = (_sx_sasl_t) p->private; + _sx_sasl_data_t sd; + char *buf, *out, *ext_id; + int i, ret, buflen, outlen, ns; + sasl_security_properties_t sec_props; + nad_t nad; + + assert((int) p); + assert((int) s); + assert((int) appname); + assert((int) mech); + + if(s->type != type_CLIENT || s->state != state_STREAM) { + _sx_debug(ZONE, "need client in stream state for sasl auth"); + return 1; + } + + /* startup */ + ret = sasl_client_init(NULL); + if(ret != SASL_OK) { + _sx_debug(ZONE, "sasl_client_init() failed (%s), not authing", sasl_errstring(ret, NULL, NULL)); + return 1; + } + + sd = (_sx_sasl_data_t) malloc(sizeof(struct _sx_sasl_data_st)); + memset(sd, 0, sizeof(struct _sx_sasl_data_st)); + + if(user != NULL) + sd->user = strdup(user); + + if(pass != NULL) { + sd->psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + strlen(pass) + 1); + strcpy(sd->psecret->data, pass); + sd->psecret->len = strlen(pass); + } + + sd->callbacks=calloc(sizeof(sasl_callback_t),4); + + /* authentication name callback */ + sd->callbacks[0].id = SASL_CB_AUTHNAME; + sd->callbacks[0].proc = &_sx_sasl_cb_get_simple; + sd->callbacks[0].context = (void *) sd; + + /* password callback */ + sd->callbacks[1].id = SASL_CB_PASS; + sd->callbacks[1].proc = &_sx_sasl_cb_get_secret; + sd->callbacks[1].context = (void *) sd; + + /* user identity callback */ + sd->callbacks[2].id = SASL_CB_USER; + sd->callbacks[2].proc = &_sx_sasl_cb_get_simple; + sd->callbacks[2].context = (void *) sd; + + /* end of callbacks */ + sd->callbacks[3].id = SASL_CB_LIST_END; + sd->callbacks[3].proc = NULL; + sd->callbacks[3].context = NULL; + + /* handshake start */ + ret = sasl_client_new(appname, (s->req_to != NULL) ? s->req_to : "", NULL, NULL, sd->callbacks, 0, &sd->sasl); + if(ret != SASL_OK) { + _sx_debug(ZONE, "sasl_client_new failed, (%s), not authing", sasl_errstring(ret, NULL, NULL)); + + free(sd->user); + free(sd->psecret); + free(sd); + + return 1; + } + + /* get external data from the ssl plugin */ + ext_id = NULL; + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->magic == SX_SASL_SSL_MAGIC) + ext_id = (char *) s->plugin_data[s->env->plugins[i]->index]; + + /* !!! XXX certs */ + /* + if(ext != NULL) { + ext->external_id = "foo"; + ext->external_ssf = 20; + } + */ + + /* if we've got some, setup for external auth */ + ret = SASL_OK; + if(ext_id != NULL) { + ret = sasl_setprop(sd->sasl, SASL_AUTH_EXTERNAL, ext_id); + if(ret == SASL_OK) ret = sasl_setprop(sd->sasl, SASL_SSF_EXTERNAL, &s->ssf); + } + + /* setup security properties */ + sec_props = ctx->sec_props; + if(s->ssf > 0) + /* if we're already encrypted, then no security layers */ + sec_props.max_ssf = 0; + + ret = sasl_setprop(sd->sasl, SASL_SEC_PROPS, &sec_props); + if(ret != SASL_OK) { + _sx_debug(ZONE, "sasl_setprop failed (%s), not authing", sasl_errstring(ret, NULL, NULL)); + + sasl_dispose(&sd->sasl); + + free(sd->user); + free(sd->psecret); + free(sd); + + return 1; + } + + /* handshake start */ + ret = sasl_client_start(sd->sasl, mech, NULL, (const char **) &out, &outlen, NULL); + if(ret != SASL_OK && ret != SASL_CONTINUE) { + _sx_debug(ZONE, "sasl_client_start failed (%s), not authing", sasl_errstring(ret, NULL, NULL)); + + sasl_dispose(&sd->sasl); + + free(sd->user); + free(sd->psecret); + free(sd); + + return 1; + } + + /* save userdata */ + s->plugin_data[p->index] = (void *) sd; + + /* in progress */ + _sx_debug(ZONE, "sending auth request to server, mech '%s': %.*s", mech, outlen, out); + + /* encode the challenge */ + _sx_sasl_encode(out, outlen, &buf, &buflen); + + /* build the nad */ + nad = nad_new(s->nad_cache); + ns = nad_add_namespace(nad, uri_SASL, NULL); + + nad_append_elem(nad, ns, "auth", 0); + nad_append_attr(nad, -1, "mechanism", mech); + if(buf != NULL) { + nad_append_cdata(nad, buf, buflen, 1); + free(buf); + } + + /* its away */ + sx_nad_write(s, nad); + + return 0; +} diff --git a/sx/sasl.h b/sx/sasl.h new file mode 100644 index 00000000..477da8aa --- /dev/null +++ b/sx/sasl.h @@ -0,0 +1,92 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifndef INCL_SX_SASL_H +#define INCL_SX_SASL_H + +#include "sx.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** init function */ +int sx_sasl_init(sx_env_t env, sx_plugin_t p, va_list args); + +/** server init flag, don't offer sasl without this */ +#define SX_SASL_OFFER (1<<3) + +/** the callback function */ +typedef int (*sx_sasl_callback_t)(int cb, void *arg, void **res, sx_t s, void *cbarg); + +/* callbacks */ +#define sx_sasl_cb_GET_REALM (0x00) +#define sx_sasl_cb_GET_PASS (0x01) +#define sx_sasl_cb_CHECK_PASS (0x02) +#define sx_sasl_cb_CHECK_AUTHZID (0x03) +#define sx_sasl_cb_GEN_AUTHZID (0x04) +#define sx_sasl_cb_CHECK_MECH (0x05) + +/* error codes */ +#define sx_sasl_ret_OK 0 +#define sx_sasl_ret_FAIL 1 + +/** trigger for client auth */ +int sx_sasl_auth(sx_plugin_t p, sx_t s, char *appname, char *mech, char *user, char *pass); + +/** our context */ +typedef struct _sx_sasl_st { + char *appname; + sasl_security_properties_t sec_props; + + sx_sasl_callback_t cb; + void *cbarg; +} *_sx_sasl_t; + +/* data for per-conncetion sasl handshakes */ +typedef struct _sx_sasl_data_st { + char *user; + sasl_secret_t *psecret; + + sasl_callback_t *callbacks; + + _sx_sasl_t ctx; + sasl_conn_t *sasl; + sx_t stream; +} *_sx_sasl_data_t; + +typedef struct sx_sasl_creds_st { + const char *authnid; + const char *realm; + const char *authzid; + const char *pass; +} *sx_sasl_creds_t; + +/** magic number of the ssl plugin, must match SX_SSL_MAGIC in sx/ssl.h */ +#define SX_SASL_SSL_MAGIC (0x01) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sx/server.c b/sx/server.c new file mode 100644 index 00000000..faa41dfb --- /dev/null +++ b/sx/server.c @@ -0,0 +1,255 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sx.h" + +static void _sx_server_notify_header(sx_t s, void *arg) { + int i, ns, len; + nad_t nad; + char *c; + sx_buf_t buf; + + _sx_debug(ZONE, "stream established"); + + /* get the plugins to setup */ + if(s->env != NULL) + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->stream != NULL) + (s->env->plugins[i]->stream)(s, s->env->plugins[i]); + + /* bump us to stream if a plugin didn't do it already */ + if(s->state < state_STREAM) { + _sx_state(s, state_STREAM); + _sx_event(s, event_STREAM, NULL); + } + + /* next, build the features */ + if(s->req_version != NULL && strcmp(s->req_version, "1.0") == 0) { + _sx_debug(ZONE, "building features nad"); + + nad = nad_new(s->nad_cache); + + ns = nad_add_namespace(nad, uri_STREAMS, "stream"); + nad_append_elem(nad, ns, "features", 0); + + /* get the plugins to populate it */ + if(s->env != NULL) + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->features != NULL) + (s->env->plugins[i]->features)(s, s->env->plugins[i], nad); + + /* new buffer for the nad */ + nad_print(nad, 0, &c, &len); + buf = _sx_buffer_new(c, len, NULL, NULL); + nad_free(nad); + + /* send this off too */ + /* !!! should this go via wnad/rnad? */ + jqueue_push(s->wbufq, buf, 0); + s->want_write = 1; + } + + /* if they sent packets before the stream was established, process the now */ + if(jqueue_size(s->rnadq) > 0 && (s->state == state_STREAM || s->state == state_OPEN)) { + _sx_debug(ZONE, "processing packets sent before stream, naughty them"); + _sx_process_read(s, _sx_buffer_new(c, 0, NULL, NULL)); + } +} + +static void _sx_server_element_start(void *arg, const char *name, const char **atts) { + sx_t s = (sx_t) arg; + int tflag = 0, fflag = 0, vflag = 0, len, i, r; + const char **attr; + char *c, id[41]; + sx_buf_t buf; + sx_error_t sxe; + + if(s->fail) return; + + /* check element and namespace */ + i = strlen(uri_STREAMS) + 7; + if(strlen(name) < i || strncmp(name, uri_STREAMS "|stream", i) != 0 || (name[i] != '\0' && name[i] != '|')) { + /* throw an error */ + _sx_gen_error(sxe, SX_ERR_STREAM, "Stream error", "Expected stream start"); + _sx_event(s, event_ERROR, (void *) &sxe); + _sx_error(s, stream_err_BAD_FORMAT, NULL); + s->fail = 1; + return; + } + + /* pull interesting things out of the header */ + attr = atts; + while(attr[0] != NULL) { + if(!tflag && strcmp(attr[0], "to") == 0) { + s->req_to = strdup(attr[1]); + tflag = 1; + } + + if(!fflag && strcmp(attr[0], "from") == 0) { + s->req_from = strdup(attr[1]); + fflag = 1; + } + + if(!vflag && strcmp(attr[0], "version") == 0) { + s->req_version = strdup(attr[1]); + vflag = 1; + } + + attr += 2; + } + + _sx_debug(ZONE, "stream request: to %s from %s version %s", s->req_to, s->req_from, s->req_version); + + /* check version */ + if(s->req_version != NULL && strcmp(s->req_version, "1.0") != 0) { + /* throw an error */ + _sx_gen_error(sxe, SX_ERR_STREAM, "Stream error", "Unsupported version"); + _sx_event(s, event_ERROR, (void *) &sxe); + _sx_error(s, stream_err_UNSUPPORTED_VERSION, NULL); + s->fail = 1; + return; + } + + /* !!! get the app to verify this stuff? */ + + /* bump */ + _sx_state(s, state_STREAM_RECEIVED); + + /* response attributes */ + if(s->req_to != NULL) s->res_from = strdup(s->req_to); + if(s->req_from != NULL) s->res_to = strdup(s->req_from); + + /* Only send 1.0 version if client has indicated a stream version - c/f XMPP 4.4.1 para 4 */ + if(s->req_version != NULL) s->res_version = strdup("1.0"); + + /* stream id */ + for(i = 0; i < 40; i++) { + r = (int) (36.0 * rand() / RAND_MAX); + id[i] = (r >= 0 && r <= 9) ? (r + 48) : (r + 87); + } + id[40] = '\0'; + + s->id = strdup(id); + + _sx_debug(ZONE, "stream id is %s", id); + + /* build the response */ + len = strlen(uri_STREAMS) + 99; + + if(s->ns != NULL) len += 9 + strlen(s->ns); + if(s->res_to != NULL) len += 6 + strlen(s->res_to); + if(s->res_from != NULL) len += 8 + strlen(s->res_from); + if(s->res_version != NULL) len += 11 + strlen(s->res_version); + + buf = _sx_buffer_new(NULL, len, _sx_server_notify_header, NULL); + + c = buf->data; + strcpy(c, "ns != NULL) { c = strchr(c, '\0'); sprintf(c, " xmlns='%s'", s->ns); } + if(s->res_to != NULL) { c = strchr(c, '\0'); sprintf(c, " to='%s'", s->res_to); } + if(s->res_from != NULL) { c = strchr(c, '\0'); sprintf(c, " from='%s'", s->res_from); } + if(s->res_version != NULL) { c = strchr(c, '\0'); sprintf(c, " version='%s'", s->res_version); } + + c = strchr(c, '\0'); sprintf(c, " id='%s'>", id); + assert(buf->len == strlen(buf->data) + 1); /* post-facto overrun detection */ + buf->len --; + + /* plugins can mess with the header too */ + if(s->env != NULL) + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->header != NULL) + (s->env->plugins[i]->header)(s, s->env->plugins[i], buf); + + _sx_debug(ZONE, "prepared stream response: %.*s", buf->len, buf->data); + + /* off it goes */ + jqueue_push(s->wbufq, buf, 0); + + s->depth++; + + /* we're alive */ + XML_SetElementHandler(s->expat, (void *) _sx_element_start, (void *) _sx_element_end); + XML_SetCharacterDataHandler(s->expat, (void *) _sx_cdata); + XML_SetStartNamespaceDeclHandler(s->expat, (void *) _sx_namespace_start); + + /* we have stuff to write */ + s->want_write = 1; +} + +static void _sx_server_element_end(void *arg, const char *name) { + sx_t s = (sx_t) arg; + + if(s->fail) return; + + s->depth--; +} + +/** catch the application namespace so we can get the response right */ +static void _sx_server_ns_start(void *arg, const char *prefix, const char *uri) { + sx_t s = (sx_t) arg; + + /* only want the default namespace */ + if(prefix != NULL) + return; + + /* sanity; MSXML-based clients have been known to send xmlns='' from time to time */ + if(uri == NULL) + return; + + /* sanity check (should never happen if expat is doing its job) */ + if(s->ns != NULL) + return; + + s->ns = strdup(uri); + + /* done */ + XML_SetStartNamespaceDeclHandler(s->expat, NULL); +} + +void sx_server_init(sx_t s, unsigned int flags) { + int i; + + assert((int) s); + + /* can't do anything if we're alive already */ + if(s->state != state_NONE) + return; + + _sx_debug(ZONE, "doing server init for sx %d", s->tag); + + s->type = type_SERVER; + s->flags = flags; + + /* plugin */ + if(s->env != NULL) + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->server != NULL) + (s->env->plugins[i]->server)(s, s->env->plugins[i]); + + /* we want to read */ + XML_SetElementHandler(s->expat, (void *) _sx_server_element_start, (void *) _sx_server_element_end); + XML_SetStartNamespaceDeclHandler(s->expat, (void *) _sx_server_ns_start); + + _sx_debug(ZONE, "waiting for stream header"); + + s->want_read = 1; + _sx_event(s, event_WANT_READ, NULL); +} diff --git a/sx/ssl.c b/sx/ssl.c new file mode 100644 index 00000000..41c2203b --- /dev/null +++ b/sx/ssl.c @@ -0,0 +1,729 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** + * this plugin implements the traditional SSL "wrappermode" streams and + * STARTTLS extension documented in xmpp-core + */ + +#include "sx.h" + +#ifdef HAVE_SSL + +#include "ssl.h" + +/* code stolen from SSL_CTX_set_verify(3) */ +static int _sx_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + char buf[256]; + X509 *err_cert; + int err, depth; + SSL *ssl; + + err_cert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + depth = X509_STORE_CTX_get_error_depth(ctx); + + /* + * Retrieve the pointer to the SSL of the connection currently treated + * and the application specific data stored into the SSL object. + */ + ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256); + + if (!preverify_ok) { + _sx_debug(ZONE, "verify error:num=%d:%s:depth=%d:%s\n", err, + X509_verify_cert_error_string(err), depth, buf); + } + else + { + _sx_debug(ZONE, "OK! depth=%d:%s", depth, buf); + } + + /* + * At this point, err contains the last verification error. We can use + * it for something special + */ + if (!preverify_ok && (err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT)) + { + X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256); + _sx_debug(ZONE, "issuer= %s\n", buf); + } + + return preverify_ok; + } + +static void _sx_ssl_starttls_notify_proceed(sx_t s, void *arg) { + _sx_debug(ZONE, "preparing for starttls"); + + _sx_reset(s); + + /* start listening */ + sx_server_init(s, s->flags | SX_SSL_WRAPPER); +} + +static int _sx_ssl_process(sx_t s, sx_plugin_t p, nad_t nad) { + int flags; + char *ns = NULL, *to = NULL, *from = NULL, *version = NULL; + sx_error_t sxe; + + /* not interested if we're a server and we never offered it */ + if(s->type == type_SERVER && !(s->flags & SX_SSL_STARTTLS_OFFER)) + return 1; + + /* only want tls packets */ + if(NAD_ENS(nad, 0) < 0 || NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_TLS) || strncmp(NAD_NURI(nad, NAD_ENS(nad, 0)), uri_TLS, strlen(uri_TLS)) != 0) + return 1; + + /* starttls from client */ + if(s->type == type_SERVER) { + if(NAD_ENAME_L(nad, 0) == 8 || strncmp(NAD_ENAME(nad, 0), "starttls", 8) == 0) { + nad_free(nad); + + /* can't go on if we've been here before */ + if(s->ssf > 0) { + _sx_debug(ZONE, "starttls requested on already encrypted channel, dropping packet"); + return 0; + } + + _sx_debug(ZONE, "starttls requested, setting up"); + + /* go ahead */ + jqueue_push(s->wbufq, _sx_buffer_new("", strlen(uri_TLS) + 19, _sx_ssl_starttls_notify_proceed, NULL), 0); + s->want_write = 1; + + /* handled the packet */ + return 0; + } + } + + else if(s->type == type_CLIENT) { + /* kick off the handshake */ + if(NAD_ENAME_L(nad, 0) == 7 || strncmp(NAD_ENAME(nad, 0), "proceed", 7) == 0) { + nad_free(nad); + + /* save interesting bits */ + flags = s->flags; + + if(s->ns != NULL) ns = strdup(s->ns); + + if(s->req_to != NULL) to = strdup(s->req_to); + if(s->req_from != NULL) from = strdup(s->req_from); + if(s->req_version != NULL) version = strdup(s->req_version); + + /* reset state */ + _sx_reset(s); + + _sx_debug(ZONE, "server ready for ssl, starting"); + + /* second time round */ + sx_client_init(s, flags | SX_SSL_WRAPPER, ns, to, from, version); + + /* free bits */ + if(ns != NULL) free(ns); + if(to != NULL) free(to); + if(from != NULL) free(from); + if(version != NULL) free(version); + + return 0; + } + + /* busted server */ + if(NAD_ENAME_L(nad, 0) == 7 || strncmp(NAD_ENAME(nad, 0), "failure", 7) == 0) { + nad_free(nad); + + /* free the pemfile arg */ + if(s->plugin_data[p->index] != NULL) + free(s->plugin_data[p->index]); + + _sx_debug(ZONE, "server can't handle ssl, business as usual"); + + _sx_gen_error(sxe, SX_ERR_STARTTLS_FAILURE, "STARTTLS failure", "Server was unable to prepare for the TLS handshake"); + _sx_event(s, event_ERROR, (void *) &sxe); + + return 0; + } + } + + _sx_debug(ZONE, "unknown starttls namespace element '%.*s', dropping packet", NAD_ENAME_L(nad, 0), NAD_ENAME(nad, 0)); + nad_free(nad); + return 0; +} + +static void _sx_ssl_features(sx_t s, sx_plugin_t p, nad_t nad) { + int ns; + + /* if the session is already encrypted, or the app told us not to, then we don't offer anything */ + if(s->state > state_STREAM || s->ssf > 0 || !(s->flags & SX_SSL_STARTTLS_OFFER)) + return; + + _sx_debug(ZONE, "offering starttls"); + + ns = nad_add_namespace(nad, uri_TLS, NULL); + nad_append_elem(nad, ns, "starttls", 1); + + if(s->flags & SX_SSL_STARTTLS_REQUIRE) + nad_append_elem(nad, ns, "required", 2); +} + +static int _sx_ssl_handshake(sx_t s, _sx_ssl_conn_t sc) { + int ret, err; + char *errstring; + sx_error_t sxe; + + /* work on establishing the channel */ + while(!SSL_is_init_finished(sc->ssl)) { + _sx_debug(ZONE, "secure channel not established, handshake in progress"); + + /* we can't handshake if they want to read, but there's nothing to read */ + if(sc->last_state == SX_SSL_STATE_WANT_READ && BIO_pending(sc->rbio) == 0) + return 0; + + /* more handshake */ + if(s->type == type_CLIENT) + ret = SSL_connect(sc->ssl); + else + ret = SSL_accept(sc->ssl); + + /* check if we're done */ + if(ret == 1) { + _sx_debug(ZONE, "secure channel established"); + sc->last_state = SX_SSL_STATE_NONE; + + s->ssf = SSL_get_cipher_bits(sc->ssl, NULL); + + _sx_debug(ZONE, "using cipher %s (%d bits)", SSL_get_cipher_name(sc->ssl), s->ssf); + + return 1; + } + + /* error checking */ + else if(ret <= 0) { + err = SSL_get_error(sc->ssl, ret); + + if(err == SSL_ERROR_WANT_READ) + sc->last_state = SX_SSL_STATE_WANT_READ; + else if(err == SSL_ERROR_WANT_WRITE) + sc->last_state = SX_SSL_STATE_WANT_WRITE; + + else { + /* fatal error */ + sc->last_state = SX_SSL_STATE_ERROR; + + errstring = ERR_error_string(ERR_get_error(), NULL); + _sx_debug(ZONE, "openssl error: %s", errstring); + + /* throw an error */ + _sx_gen_error(sxe, SX_ERR_SSL, "SSL handshake error", errstring); + _sx_event(s, event_ERROR, (void *) &sxe); + + _sx_error(s, stream_err_INTERNAL_SERVER_ERROR, errstring); + _sx_close(s); + + /* !!! drop queue */ + + return -1; + } + } + } + + return 1; +} + +static int _sx_ssl_wio(sx_t s, sx_plugin_t p, sx_buf_t buf) { + _sx_ssl_conn_t sc = (_sx_ssl_conn_t) s->plugin_data[p->index]; + int est, ret, err; + sx_buf_t wbuf; + char *errstring; + sx_error_t sxe; + + /* sanity */ + if(sc->last_state == SX_SSL_STATE_ERROR) + return -2; + + _sx_debug(ZONE, "in _sx_ssl_wio"); + + /* queue the buffer */ + if(buf->len > 0) { + _sx_debug(ZONE, "queueing buffer for write"); + + jqueue_push(sc->wq, _sx_buffer_new(buf->data, buf->len, buf->notify, buf->notify_arg), 0); + _sx_buffer_clear(buf); + buf->notify = NULL; + buf->notify_arg = NULL; + } + + /* handshake */ + est = _sx_ssl_handshake(s, sc); + if(est < 0) + return -2; /* fatal error */ + + /* channel established, do some real writing */ + wbuf = NULL; + if(est > 0 && jqueue_size(sc->wq) > 0) { + _sx_debug(ZONE, "preparing queued buffer for write"); + + wbuf = jqueue_pull(sc->wq); + + ret = SSL_write(sc->ssl, wbuf->data, wbuf->len); + if(ret <= 0) { + /* something's wrong */ + _sx_debug(ZONE, "write failed, requeuing buffer"); + + /* requeue the buffer */ + jqueue_push(sc->wq, wbuf, (sc->wq->front != NULL) ? sc->wq->front->priority + 1 : 0); + + /* error checking */ + err = SSL_get_error(sc->ssl, ret); + + if(err == SSL_ERROR_ZERO_RETURN) { + /* ssl channel closed, we're done */ + _sx_close(s); + } + + if(err == SSL_ERROR_WANT_READ) { + /* we'll be renegotiating next time */ + _sx_debug(ZONE, "renegotiation started"); + sc->last_state = SX_SSL_STATE_WANT_READ; + } + + else { + sc->last_state = SX_SSL_STATE_ERROR; + + /* something very bad */ + errstring = ERR_error_string(ERR_get_error(), NULL); + _sx_debug(ZONE, "openssl error: %s", errstring); + + /* throw an error */ + _sx_gen_error(sxe, SX_ERR_SSL, "SSL handshake error", errstring); + _sx_event(s, event_ERROR, (void *) &sxe); + + _sx_error(s, stream_err_INTERNAL_SERVER_ERROR, errstring); + _sx_close(s); + + /* !!! drop queue */ + + return -2; /* fatal */ + } + } + } + + /* prepare the buffer with stuff to write */ + if(BIO_pending(sc->wbio) > 0) { + int bytes_pending = BIO_pending(sc->wbio); + assert(buf->len == 0); + _sx_buffer_alloc_margin(buf, 0, bytes_pending); + BIO_read(sc->wbio, buf->data, bytes_pending); + buf->len += bytes_pending; + + /* restore notify and clean up */ + if(wbuf != NULL) { + buf->notify = wbuf->notify; + buf->notify_arg = wbuf->notify_arg; + _sx_buffer_free(wbuf); + } + + _sx_debug(ZONE, "prepared %d ssl bytes for write", buf->len); + } + + /* flag if we want to read */ + if(sc->last_state == SX_SSL_STATE_WANT_READ || sc->last_state == SX_SSL_STATE_NONE) + s->want_read = 1; + + return 1; +} + +static int _sx_ssl_rio(sx_t s, sx_plugin_t p, sx_buf_t buf) { + _sx_ssl_conn_t sc = (_sx_ssl_conn_t) s->plugin_data[p->index]; + int est, ret, err, pending; + char *errstring; + sx_error_t sxe; + + /* sanity */ + if(sc->last_state == SX_SSL_STATE_ERROR) + return -1; + + _sx_debug(ZONE, "in _sx_ssl_rio"); + + /* move the data into the ssl read buffer */ + if(buf->len > 0) { + _sx_debug(ZONE, "loading %d bytes into ssl read buffer", buf->len); + + BIO_write(sc->rbio, buf->data, buf->len); + + _sx_buffer_clear(buf); + } + + /* handshake */ + est = _sx_ssl_handshake(s, sc); + if(est < 0) + return -1; /* fatal error */ + + /* channel is up, slurp up the read buffer */ + if(est > 0) { + + pending = SSL_pending(sc->ssl); + if(pending == 0) + pending = BIO_pending(sc->rbio); + + /* get it all */ + while((pending = SSL_pending(sc->ssl)) > 0 || (pending = BIO_pending(sc->rbio)) > 0) { + _sx_buffer_alloc_margin(buf, 0, pending); + + ret = SSL_read(sc->ssl, &(buf->data[buf->len]), pending); + + if (ret == 0) + { + /* ret will equal zero if the SSL stream was closed. + (See the SSL_read manpage.) */ + + /* If the SSL Shutdown happened properly, + (i.e. we got an SSL "close notify") + then proccess the last packet recieved. */ + if (SSL_get_shutdown(sc->ssl) == SSL_RECEIVED_SHUTDOWN) + { + _sx_close(s); + break; + } + + /* If the SSL stream was just closed and not shutdown, + drop the last packet recieved. + WARNING: This may cause clients that use SSLv2 and + earlier to not log out properly. */ + + err = SSL_get_error(sc->ssl, ret); + + _sx_buffer_clear(buf); + + + if(err == SSL_ERROR_ZERO_RETURN) { + /* ssl channel closed, we're done */ + _sx_close(s); + } + + return -1; + } + else if(ret < 0) { + /* ret will be negative if the SSL stream needs + more data, or if there was a SSL error. + (See the SSL_read manpage.) */ + err = SSL_get_error(sc->ssl, ret); + + /* ssl block incomplete, need more */ + if(err == SSL_ERROR_WANT_READ) { + sc->last_state = SX_SSL_STATE_WANT_READ; + + break; + } + + /* something's wrong */ + _sx_buffer_clear(buf); + + + /* !!! need checks for renegotiation */ + + sc->last_state = SX_SSL_STATE_ERROR; + + errstring = ERR_error_string(ERR_get_error(), NULL); + _sx_debug(ZONE, "openssl error: %s", errstring); + + /* throw an error */ + _sx_gen_error(sxe, SX_ERR_SSL, "SSL handshake error", errstring); + _sx_event(s, event_ERROR, (void *) &sxe); + + _sx_error(s, stream_err_INTERNAL_SERVER_ERROR, errstring); + _sx_close(s); + + /* !!! drop queue */ + + return -1; + } + + buf->len += ret; + } + } + + /* flag if stuff to write */ + if(BIO_pending(sc->wbio) > 0 || (est > 0 && jqueue_size(sc->wq) > 0)) + s->want_write = 1; + + /* flag if we want to read */ + if(sc->last_state == SX_SSL_STATE_WANT_READ || sc->last_state == SX_SSL_STATE_NONE) + s->want_read = 1; + + if(buf->len == 0) + return 0; + + return 1; +} + +static void _sx_ssl_client(sx_t s, sx_plugin_t p) { + _sx_ssl_conn_t sc; + char *pemfile; + int ret; + + /* only bothering if they asked for wrappermode */ + if(!(s->flags & SX_SSL_WRAPPER) || s->ssf > 0) + return; + + _sx_debug(ZONE, "preparing for ssl connect for %d", s->tag); + + sc = (_sx_ssl_conn_t) malloc(sizeof(struct _sx_ssl_conn_st)); + memset(sc, 0, sizeof(struct _sx_ssl_conn_st)); + + /* create the buffers */ + sc->rbio = BIO_new(BIO_s_mem()); + sc->wbio = BIO_new(BIO_s_mem()); + + /* new ssl conn */ + sc->ssl = SSL_new((SSL_CTX *) p->private); + SSL_set_bio(sc->ssl, sc->rbio, sc->wbio); + SSL_set_connect_state(sc->ssl); + + /* alternate pemfile */ + /* !!! figure out how to error correctly here - just returning will cause + * us to send a normal unencrypted stream start while the server is + * waiting for ClientHelo. the server will flag an error, but it won't + * help the admin at all to figure out what happened */ + pemfile = s->plugin_data[p->index]; s->plugin_data[p->index] = NULL; + if(pemfile != NULL) { + /* load the certificate */ + ret = SSL_use_certificate_file(sc->ssl, pemfile, SSL_FILETYPE_PEM); + if(ret != 1) { + _sx_debug(ZONE, "couldn't load alternate certificate from %s", pemfile); + SSL_free(sc->ssl); + free(sc); + free(pemfile); + return; + } + + /* load the private key */ + ret = SSL_use_PrivateKey_file(sc->ssl, pemfile, SSL_FILETYPE_PEM); + if(ret != 1) { + _sx_debug(ZONE, "couldn't load alternate private key from %s", pemfile); + SSL_free(sc->ssl); + free(sc); + free(pemfile); + return; + } + + /* check the private key matches the certificate */ + ret = SSL_check_private_key(sc->ssl); + if(ret != 1) { + _sx_debug(ZONE, "private key does not match certificate public key"); + SSL_free(sc->ssl); + free(sc); + free(pemfile); + return; + } + + _sx_debug(ZONE, "loaded alternate pemfile %s", pemfile); + + free(pemfile); + } + + /* buffer queue */ + sc->wq = jqueue_new(); + + s->plugin_data[p->index] = (void *) sc; + + /* bring the plugin online */ + _sx_chain_io_plugin(s, p); +} + +static void _sx_ssl_server(sx_t s, sx_plugin_t p) { + _sx_ssl_conn_t sc; + + /* only bothering if they asked for wrappermode */ + if(!(s->flags & SX_SSL_WRAPPER) || s->ssf > 0) + return; + + _sx_debug(ZONE, "preparing for ssl accept for %d", s->tag); + + sc = (_sx_ssl_conn_t) malloc(sizeof(struct _sx_ssl_conn_st)); + memset(sc, 0, sizeof(struct _sx_ssl_conn_st)); + + /* create the buffers */ + sc->rbio = BIO_new(BIO_s_mem()); + sc->wbio = BIO_new(BIO_s_mem()); + + /* new ssl conn */ + sc->ssl = SSL_new((SSL_CTX *) p->private); + SSL_set_bio(sc->ssl, sc->rbio, sc->wbio); + SSL_set_accept_state(sc->ssl); + + /* buffer queue */ + sc->wq = jqueue_new(); + + s->plugin_data[p->index] = (void *) sc; + + /* bring the plugin online */ + _sx_chain_io_plugin(s, p); +} + +/** cleanup */ +static void _sx_ssl_free(sx_t s, sx_plugin_t p) { + _sx_ssl_conn_t sc = (_sx_ssl_conn_t) s->plugin_data[p->index]; + sx_buf_t buf; + + if(sc == NULL) + return; + + log_debug(ZONE, "cleaning up conn state"); + + if(s->type == type_NONE) { + free(sc); + return; + } + + if(sc->external_id != NULL) free(sc->external_id); + + if(sc->ssl) SSL_free(sc->ssl); /* frees wbio and rbio too */ + + while((buf = jqueue_pull(sc->wq)) != NULL) + _sx_buffer_free(buf); + + jqueue_free(sc->wq); + + free(sc); +} + +static void _sx_ssl_unload(sx_plugin_t p) { + SSL_CTX_free((SSL_CTX *) p->private); +} + +/** args: pemfile */ +int sx_ssl_init(sx_env_t env, sx_plugin_t p, va_list args) { + char *pemfile, *cachain; + SSL_CTX *ctx; + int ret; + int mode; + + _sx_debug(ZONE, "initialising ssl plugin"); + + pemfile = va_arg(args, char *); + if(pemfile == NULL) + return 1; + + if(p->private != NULL) + return 1; + + cachain = va_arg(args, char *); + mode = va_arg(args, int); + + /* !!! output openssl error messages to the debug log */ + + /* openssl startup */ + SSL_library_init(); + SSL_load_error_strings(); + + /* create the context */ + ctx = SSL_CTX_new(SSLv23_method()); + if(ctx == NULL) { + _sx_debug(ZONE, "ssl context creation failed"); + return 1; + } + + /* load the certificate */ + ret = SSL_CTX_use_certificate_file(ctx, pemfile, SSL_FILETYPE_PEM); + if(ret != 1) { + _sx_debug(ZONE, "couldn't load certificate from %s", pemfile); + SSL_CTX_free(ctx); + return 1; + } + + /* load the private key */ + ret = SSL_CTX_use_PrivateKey_file(ctx, pemfile, SSL_FILETYPE_PEM); + if(ret != 1) { + _sx_debug(ZONE, "couldn't load private key from %s", pemfile); + SSL_CTX_free(ctx); + return 1; + } + + /* Load the CA chain, if configured */ + if (cachain != NULL) { + ret = SSL_CTX_load_verify_locations (ctx, cachain, NULL); + if(ret != 1) { + _sx_debug(ZONE, "WARNING: couldn't load CA chain: %s", cachain); + } + } + + /* check the private key matches the certificate */ + ret = SSL_CTX_check_private_key(ctx); + if(ret != 1) { + _sx_debug(ZONE, "private key does not match certificate public key"); + SSL_CTX_free(ctx); + return 1; + } + + _sx_debug(ZONE, "Setting verify mode to %02x", mode); + SSL_CTX_set_verify(ctx, mode, _sx_ssl_verify_callback); + + /* its good */ + _sx_debug(ZONE, "ssl context initialised; certificate and key loaded from %s", pemfile); + + p->magic = SX_SSL_MAGIC; + + p->private = (void *) ctx; + + p->unload = _sx_ssl_unload; + + p->client = _sx_ssl_client; + p->server = _sx_ssl_server; + p->rio = _sx_ssl_rio; + p->wio = _sx_ssl_wio; + p->features = _sx_ssl_features; + p->process = _sx_ssl_process; + p->free = _sx_ssl_free; + + return 0; +} + +int sx_ssl_client_starttls(sx_plugin_t p, sx_t s, char *pemfile) { + assert((int) p); + assert((int) s); + + /* sanity */ + if(s->type != type_CLIENT || s->state != state_STREAM) { + _sx_debug(ZONE, "wrong conn type or state for client starttls"); + return 1; + } + + /* check if we're already encrypted */ + if(s->ssf > 0) { + _sx_debug(ZONE, "encrypted channel already established"); + return 1; + } + + _sx_debug(ZONE, "initiating starttls sequence"); + + /* save the given pemfile for later */ + if(pemfile != NULL) + s->plugin_data[p->index] = (void *) strdup(pemfile); + + /* go */ + jqueue_push(s->wbufq, _sx_buffer_new("", strlen(uri_TLS) + 20, NULL, NULL), 0); + s->want_write = 1; + _sx_event(s, event_WANT_WRITE, NULL); + + return 0; +} + +#endif diff --git a/sx/ssl.h b/sx/ssl.h new file mode 100644 index 00000000..95265bbc --- /dev/null +++ b/sx/ssl.h @@ -0,0 +1,79 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifndef INCL_SX_SSL_H +#define INCL_SX_SSL_H + +#include "sx.h" + +#ifdef HAVE_SSL + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** our magic, so plugins can find us */ +#define SX_SSL_MAGIC (0x01) + +/** init function */ +int sx_ssl_init(sx_env_t env, sx_plugin_t p, va_list args); + +/* flags for client/server init */ +#define SX_SSL_WRAPPER (1<<0) +#define SX_SSL_STARTTLS_OFFER (1<<1) +#define SX_SSL_STARTTLS_REQUIRE (1<<2) + +/** trigger for client starttls */ +int sx_ssl_client_starttls(sx_plugin_t p, sx_t s, char *pemfile); + +/** error code */ +#define SX_ERR_SSL (0x010) +#define SX_ERR_STARTTLS_FAILURE (0x011) + +/* previous states */ +#define SX_SSL_STATE_NONE (0) +#define SX_SSL_STATE_WANT_READ (1) +#define SX_SSL_STATE_WANT_WRITE (2) +#define SX_SSL_STATE_ERROR (3) + +/** a single conn */ +typedef struct _sx_ssl_conn_st { + /* id and ssf for sasl external auth */ + char *external_id; + + SSL *ssl; + + BIO *wbio, *rbio; + + jqueue_t wq; + + int last_state; +} *_sx_ssl_conn_t; + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_SSL */ + +#endif diff --git a/sx/sx.c b/sx/sx.c new file mode 100644 index 00000000..04d03985 --- /dev/null +++ b/sx/sx.c @@ -0,0 +1,329 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "sx.h" + +sx_t sx_new(sx_env_t env, int tag, sx_callback_t cb, void *arg) { + sx_t s; + int i; + + assert((int) cb); + + s = (sx_t) malloc(sizeof(struct _sx_st)); + memset(s, 0, sizeof(struct _sx_st)); + + s->env = env; + s->tag = tag; + s->cb = cb; + s->cb_arg = arg; + + s->expat = XML_ParserCreateNS(NULL, '|'); + XML_SetReturnNSTriplet(s->expat, 1); + XML_SetUserData(s->expat, (void *) s); + + s->nad_cache = nad_cache_new(); + + s->wbufq = jqueue_new(); + s->rnadq = jqueue_new(); + + if(env != NULL) { + s->plugin_data = (void **) malloc(sizeof(void *) * env->nplugins); + memset(s->plugin_data, 0, sizeof(void *) * env->nplugins); + + for(i = 0; i < env->nplugins; i++) + if(env->plugins[i]->new != NULL) + (env->plugins[i]->new)(s, env->plugins[i]); + } + + _sx_debug(ZONE, "allocated new sx for %d", tag); + + return s; +} + +void sx_free(sx_t s) { + sx_buf_t buf; + nad_t nad; + int i; + _sx_chain_t scan, next; + + if (s == NULL) + return; + + /* we are not reentrant */ + assert(!s->reentry); + + _sx_debug(ZONE, "freeing sx for %d", s->tag); + + if(s->ns != NULL) free(s->ns); + + if(s->req_to != NULL) free(s->req_to); + if(s->req_from != NULL) free(s->req_from); + if(s->req_version != NULL) free(s->req_version); + + if(s->res_to != NULL) free(s->res_to); + if(s->res_from != NULL) free(s->res_from); + if(s->res_version != NULL) free(s->res_version); + + if(s->id != NULL) free(s->id); + + while((buf = jqueue_pull(s->wbufq)) != NULL) + _sx_buffer_free(buf); + if (s->wbufpending != NULL) + _sx_buffer_free(s->wbufpending); + + while((nad = jqueue_pull(s->rnadq)) != NULL) + nad_free(nad); + + jqueue_free(s->wbufq); + jqueue_free(s->rnadq); + + XML_ParserFree(s->expat); + + if(s->nad != NULL) nad_free(s->nad); + nad_cache_free(s->nad_cache); + + if(s->auth_method != NULL) free(s->auth_method); + if(s->auth_id != NULL) free(s->auth_id); + + if(s->env != NULL) { + for(i = 0; i < s->env->nplugins; i++) + if(s->env->plugins[i]->free != NULL) + (s->env->plugins[i]->free)(s, s->env->plugins[i]); + + scan = s->wio; + while(scan != NULL) { + next = scan->wnext; + free(scan); + scan = next; + } + + scan = s->wnad; + while(scan != NULL) { + next = scan->wnext; + free(scan); + scan = next; + } + + free(s->plugin_data); + } + + free(s); +} + +/** force advance into auth state */ +void sx_auth(sx_t s, const char *auth_method, const char *auth_id) { + assert((int) s); + + _sx_debug(ZONE, "authenticating stream (method=%s; id=%s)", auth_method, auth_id); + + if(auth_method != NULL) s->auth_method = strdup(auth_method); + if(auth_id != NULL) s->auth_id = strdup(auth_id); + + _sx_state(s, state_OPEN); + _sx_event(s, event_OPEN, NULL); +} + +/** utility; reset stream state */ +void _sx_reset(sx_t s) { + struct _sx_st temp; + sx_t new; + + _sx_debug(ZONE, "resetting stream state"); + + /* we want to reset the contents of s, but we can't free s because + * the caller (and others) hold references. so, we make a new sx_t, + * copy the contents (only pointers), free it (which will free strings + * and queues), then make another new one, and copy the contents back + * into s */ + + temp.env = s->env; + temp.tag = s->tag; + temp.cb = s->cb; + temp.cb_arg = s->cb_arg; + + temp.flags = s->flags; + temp.reentry = s->reentry; + temp.ssf = s->ssf; + temp.wio = s->wio; + temp.rio = s->rio; + temp.wnad = s->wnad; + temp.rnad = s->rnad; + temp.plugin_data = s->plugin_data; + + s->reentry = 0; + + s->env = NULL; /* we get rid of this, because we don't want plugin data to be freed */ + + new = (sx_t) malloc(sizeof(struct _sx_st)); + memcpy(new, s, sizeof(struct _sx_st)); + sx_free(new); + + new = sx_new(NULL, temp.tag, temp.cb, temp.cb_arg); + memcpy(s, new, sizeof(struct _sx_st)); + free(new); + + /* massaged expat into shape */ + XML_SetUserData(s->expat, (void *) s); + + s->env = temp.env; + s->flags = temp.flags; + s->reentry = temp.reentry; + s->ssf = temp.ssf; + s->wio = temp.wio; + s->rio = temp.rio; + s->wnad = temp.wnad; + s->rnad = temp.rnad; + s->plugin_data = temp.plugin_data; + + s->has_reset = 1; +} + +/** utility: make a new buffer + if len>0 but data is NULL, the buffer will contain that many bytes + of garbage, to be overwritten by caller. otherwise, data pointed to + by 'data' will be copied into buf */ +sx_buf_t _sx_buffer_new(char *data, int len, _sx_notify_t notify, void *notify_arg) { + sx_buf_t buf; + + buf = (sx_buf_t) malloc(sizeof(struct _sx_buf_st)); + + if (len <= 0) { + buf->data = buf->heap = NULL; + buf->len = 0; + } else { + buf->data = buf->heap = (char *) malloc(sizeof(char) * len); + if(data != NULL) + memcpy(buf->data, data, len); +#ifndef NDEBUG + else + memset(buf->data, '$', len); /* catch uninitialized use */ +#endif + buf->len = len; + } + + buf->notify = notify; + buf->notify_arg = notify_arg; + + return buf; +} + +/** utility: kill a buffer */ +void _sx_buffer_free(sx_buf_t buf) { + if(buf->heap != NULL) + free(buf->heap); + + free(buf); +} + +/** utility: clear out a buffer, but don't deallocate it */ +void _sx_buffer_clear(sx_buf_t buf) { + if(buf->heap != NULL) { + free(buf->heap); + buf->heap = NULL; + } + buf->data = NULL; + buf->len = 0; +} + +/** utility: ensure a certain amount of allocated space adjacent to buf->data */ +void _sx_buffer_alloc_margin(sx_buf_t buf, int before, int after) +{ + char *new_heap; + + assert( before >= 0 ); + assert( after >= 0 ); + + /* If there wasn't any data in the buf, we can just allocate space for the margins */ + if (buf->data == NULL || buf->len == 0) { + if (buf->heap != NULL) + buf->heap = realloc(buf->heap, before+after); + else + buf->heap = malloc(before+after); + buf->data = buf->heap + before; + return; + } + + if (buf->heap != NULL) { + int old_leader = buf->data - buf->heap; + /* Hmmm, maybe we can just call realloc() ? */ + if (old_leader >= before && old_leader <= (before * 4)) { + buf->heap = realloc(buf->heap, before + buf->len + after); + buf->data = buf->heap + old_leader; + return; + } + } + + /* Most general case --- allocate a new buffer, copy stuff over, free the old one. */ + new_heap = malloc(before + buf->len + after); + memcpy(new_heap + before, buf->data, buf->len); + if (buf->heap != NULL) + free(buf->heap); + buf->heap = new_heap; + buf->data = new_heap + before; +} + +/** utility: reset a sx_buf_t's contents. If newheap is non-NULL it is assumed to be 'data's malloc block and ownership of the block is taken by the buffer. If newheap is NULL then the data is copied. */ +void _sx_buffer_set(sx_buf_t buf, char *newdata, int newlength, char *newheap) +{ + if (newheap == NULL) { + buf->len = 0; + _sx_buffer_alloc_margin(buf, 0, newlength); + if (newlength > 0) + memcpy(buf->data, newdata, newlength); + buf->len = newlength; + return; + } + + _sx_buffer_clear(buf); + buf->data = newdata; + buf->len = newlength; + buf->heap = newheap; +} + +/** debug macro helpers */ +void __sx_debug(char *file, int line, const char *msgfmt, ...) { + va_list ap; + char *pos, message[MAX_DEBUG]; + int sz; + + /* insert the header */ + snprintf(message, MAX_DEBUG, "sx (%s:%d) ", file, line); + + /* find the end and attach the rest of the msg */ + for (pos = message; *pos != '\0'; pos++); /*empty statement */ + sz = pos - message; + va_start(ap, msgfmt); + vsnprintf(pos, MAX_DEBUG - sz, msgfmt, ap); + fprintf(stderr,"%s", message); + fprintf(stderr, "\n"); + fflush(stderr); +} + +int __sx_event(char *file, int line, sx_t s, sx_event_t e, void *data) { + int ret; + + _sx_debug(file, line, "tag %d event %d data 0x%x", s->tag, e, data); + + s->reentry++; + ret = (s->cb)(s, e, data, s->cb_arg); + s->reentry--; + + return ret; +} diff --git a/sx/sx.h b/sx/sx.h new file mode 100644 index 00000000..bdb19a2c --- /dev/null +++ b/sx/sx.h @@ -0,0 +1,376 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifndef INCL_SX_H +#define INCL_SX_H + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ac-stdint.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* forward declarations */ +typedef struct _sx_st *sx_t; +typedef struct _sx_env_st *sx_env_t; +typedef struct _sx_plugin_st *sx_plugin_t; + +/** things that can happen */ +typedef enum { + event_WANT_READ, /* we want read actions */ + event_WANT_WRITE, /* we want write actions */ + event_READ, /* read some stuff for me */ + event_WRITE, /* write this to the fd */ + event_STREAM, /* stream is ready to go */ + event_OPEN, /* normal operation */ + event_PACKET, /* got a packet */ + event_CLOSED, /* its over */ + event_ERROR /* something's wrong */ +} sx_event_t; + +/** connection states */ +typedef enum { + state_NONE, /* pre-init */ + state_STREAM_RECEIVED, /* stream start received (server) */ + state_STREAM_SENT, /* stream start sent (client) */ + state_STREAM, /* stream established */ + state_OPEN, /* auth completed (normal stream operation) */ + state_CLOSING, /* ready to close (send event_CLOSED to app) */ + state_CLOSED /* closed (same as NONE, but can't be used any more) */ +} _sx_state_t; + +/** connection types */ +typedef enum { + type_NONE, + type_CLIENT, /* we initiated the connection */ + type_SERVER /* they initiated */ +} _sx_type_t; + +/** event callback */ +typedef int (*sx_callback_t)(sx_t s, sx_event_t e, void *data, void *arg); + +/** plugin init */ +typedef int (*sx_plugin_init_t)(sx_env_t env, sx_plugin_t p, va_list args); + +/* errors */ +#define SX_SUCCESS (0x00) +#define SX_ERR_STREAM (0x01) +#define SX_ERR_AUTH (0x02) +#define SX_ERR_XML_PARSE (0x03) + +/** error info for event_ERROR */ +typedef struct _sx_error_st { + int code; + char *generic; + char *specific; +} sx_error_t; + +/** helper macro to populate this struct */ +#define _sx_gen_error(e,c,g,s) do { e.code = c; e.generic = g; e.specific = s; } while(0); + +/** prototype for the write notify function */ +typedef void (*_sx_notify_t)(sx_t s, void *arg); + +/** utility: buffer */ +typedef struct _sx_buf_st *sx_buf_t; +struct _sx_buf_st { + char *data; /* pointer to buffer's data */ + int len; /* length of buffer's data */ + char *heap; /* beginning of malloc() block containing data, if non-NULL */ + + /* function to call when this buffer gets written */ + _sx_notify_t notify; + void *notify_arg; +}; + +/* stream errors */ +#define stream_err_BAD_FORMAT (0) +#define stream_err_BAD_NAMESPACE_PREFIX (1) +#define stream_err_CONFLICT (2) +#define stream_err_CONNECTION_TIMEOUT (3) +#define stream_err_HOST_GONE (4) +#define stream_err_HOST_UNKNOWN (5) +#define stream_err_IMPROPER_ADDRESSING (6) +#define stream_err_INTERNAL_SERVER_ERROR (7) +#define stream_err_INVALID_FROM (8) +#define stream_err_INVALID_ID (9) +#define stream_err_INVALID_NAMESPACE (10) +#define stream_err_INVALID_XML (11) +#define stream_err_NOT_AUTHORIZED (12) +#define stream_err_POLICY_VIOLATION (13) +#define stream_err_REMOTE_CONNECTION_FAILED (14) +#define stream_err_RESTRICTED_XML (15) +#define stream_err_RESOURCE_CONSTRAINT (16) +#define stream_err_SEE_OTHER_HOST (17) +#define stream_err_SYSTEM_SHUTDOWN (18) +#define stream_err_UNDEFINED_CONDITION (19) +#define stream_err_UNSUPPORTED_ENCODING (20) +#define stream_err_UNSUPPORTED_STANZA_TYPE (21) +#define stream_err_UNSUPPORTED_VERSION (22) +#define stream_err_XML_NOT_WELL_FORMED (23) +#define stream_err_LAST (24) + +/* exported functions */ + +/* make/break */ +sx_t sx_new(sx_env_t env, int tag, sx_callback_t cb, void *arg); +void sx_free(sx_t s); + +/* get things ready */ +void sx_client_init(sx_t s, unsigned int flags, char *ns, char *to, char *from, char *version); +void sx_server_init(sx_t s, unsigned int flags); + +/* activity on socket, do stuff! (returns 1 if more read/write actions wanted, 0 otherwise) */ +int sx_can_read(sx_t s); +int sx_can_write(sx_t s); + +/** sending a nad */ +void sx_nad_write_elem(sx_t s, nad_t nad, int elem); +#define sx_nad_write(s,nad) sx_nad_write_elem(s, nad, 0) + +/** sending raw data */ +void sx_raw_write(sx_t s, char *buf, int len); + +/** authenticate the stream and move to the auth'd state */ +void sx_auth(sx_t s, const char *auth_method, const char *auth_id); + +/* make/break an environment */ +sx_env_t sx_env_new(void); +void sx_env_free(sx_env_t env); + +/** load a plugin into the environment */ +sx_plugin_t sx_env_plugin(sx_env_t env, sx_plugin_init_t init, ...); + +/* send errors and close stuff */ +void sx_error(sx_t s, int err, char *text); +void sx_close(sx_t s); +void sx_kill(sx_t s); + + +/* internal functions */ + +/* primary expat callbacks */ +void _sx_element_start(void *arg, const char *name, const char **atts); +void _sx_element_end(void *arg, const char *name); +void _sx_cdata(void *arg, const char *str, int len); +void _sx_namespace_start(void *arg, const char *prefix, const char *uri); + +/** processor for incoming wire data */ +void _sx_process_read(sx_t s, sx_buf_t buf); + +/** main nad processor */ +void _sx_nad_process(sx_t s, nad_t nad); + +/* chain management */ +void _sx_chain_io_plugin(sx_t s, sx_plugin_t p); +void _sx_chain_nad_plugin(sx_t s, sx_plugin_t p); + +/* chain running */ +int _sx_chain_io_write(sx_t s, sx_buf_t buf); +int _sx_chain_io_read(sx_t s, sx_buf_t buf); + +int _sx_chain_nad_write(sx_t s, nad_t nad, int elem); +int _sx_chain_nad_read(sx_t s, nad_t nad); + +/* buffer utilities */ +sx_buf_t _sx_buffer_new(char *data, int len, _sx_notify_t notify, void *notify_arg); +void _sx_buffer_free(sx_buf_t buf); +void _sx_buffer_clear(sx_buf_t buf); +void _sx_buffer_alloc_margin(sx_buf_t buf, int before, int after); +void _sx_buffer_set(sx_buf_t buf, char *newdata, int newlength, char *newheap); + +/** sending a nad (internal) */ +int _sx_nad_write(sx_t s, nad_t nad, int elem); + +/** sending raw data (internal) */ +void sx_raw_write(sx_t s, char *buf, int len); + +/** reset stream state without informing the app */ +void _sx_reset(sx_t s); + +/* send errors and close stuff */ +void _sx_error(sx_t s, int err, char *text); +void _sx_close(sx_t s); + +/** read/write plugin chain */ +typedef struct _sx_chain_st *_sx_chain_t; +struct _sx_chain_st { + sx_plugin_t p; + + _sx_chain_t wnext; /* -> write */ + _sx_chain_t rnext; /* <- read */ +}; + +/** holds the state for a single stream */ +struct _sx_st { + /* environment */ + sx_env_t env; + + /* tag, for logging */ + int tag; + + /* callback */ + sx_callback_t cb; + void *cb_arg; + + /* type */ + _sx_type_t type; + + /* flags */ + unsigned int flags; + + /* application namespace */ + char *ns; + + /* requested stream properties */ + char *req_to; + char *req_from; + char *req_version; + + /* responded stream properties */ + char *res_to; + char *res_from; + char *res_version; + + /* stream id */ + char *id; + + /* io chain */ + _sx_chain_t wio, rio; + + /* nad chain */ + _sx_chain_t wnad, rnad; + + /* internal queues */ + jqueue_t wbufq; /* buffers waiting to go to wio */ + sx_buf_t wbufpending; /* buffer passed through wio but not written yet */ + jqueue_t rnadq; /* completed nads waiting to go to rnad */ + + /* do we want to read or write? */ + int want_read, want_write; + + /* current state */ + _sx_state_t state; + + /* parser */ + XML_Parser expat; + int depth; + int fail; + + /* nad cache and nad currently being built */ + nad_cache_t nad_cache; + nad_t nad; + + /* plugin storage */ + void **plugin_data; + + /* type and id of auth */ + char *auth_method; + char *auth_id; + + /* if true, then we were called from the callback */ + int reentry; + + /* this is true after a stream resets - applications should check this before doing per-stream init */ + int has_reset; + + /* security strength factor (in sasl parlance) - roughly equivalent to key strength */ + int ssf; +}; + +/** a plugin */ +struct _sx_plugin_st { + sx_env_t env; + + int magic; /* unique id so that plugins can find each other */ + + int index; + + void *private; + + void (*new)(sx_t s, sx_plugin_t p); /* pre-run init */ + void (*free)(sx_t s, sx_plugin_t p); /* conn being freed */ + + void (*client)(sx_t s, sx_plugin_t p); /* client init */ + void (*server)(sx_t s, sx_plugin_t p); /* server init */ + + /* return -2 == failed (permanent), -1 == failed (temporary), 0 == handled, 1 == pass */ + int (*wio)(sx_t s, sx_plugin_t p, sx_buf_t buf); /* before being written */ + int (*rio)(sx_t s, sx_plugin_t p, sx_buf_t buf); /* after being read */ + + /* return 0 == handled, 1 == pass */ + int (*wnad)(sx_t s, sx_plugin_t p, nad_t nad, int elem); /* before being written */ + int (*rnad)(sx_t s, sx_plugin_t p, nad_t nad); /* after being read */ + + void (*header)(sx_t s, sx_plugin_t p, sx_buf_t buf); /* before header req/res write */ + void (*stream)(sx_t s, sx_plugin_t p); /* after-stream init */ + + void (*features)(sx_t s, sx_plugin_t p, nad_t nad); /* offer features */ + + /* return 0 == handled, 1 == pass */ + int (*process)(sx_t s, sx_plugin_t p, nad_t nad); /* process completed nads */ + + void (*unload)(sx_plugin_t p); /* plugin unloading */ +}; + +/** an environment */ +struct _sx_env_st { + sx_plugin_t *plugins; + int nplugins; +}; + +/** debugging macros */ +#define ZONE __FILE__,__LINE__ + +/** helper functions for macros when we're debugging */ +void __sx_debug(char *file, int line, const char *msgfmt, ...); + +/** helper and internal macro for firing the callback */ +int __sx_event(char *file, int line, sx_t s, sx_event_t e, void *data); +#define _sx_event(s,e,data) __sx_event(ZONE, s, e, data) + +#ifdef SX_DEBUG + +/** print debug output */ +#define _sx_debug if(get_debug_flag()) __sx_debug + +/** state changes with output */ +#define _sx_state(s,st) do { _sx_debug(ZONE, "%d state change from %d to %d", s->tag, s->state, st); s->state = st; } while(0) + +#else + +/* clean and efficient versions */ +#define _sx_debug if(0) __sx_debug +#define _sx_state(s,st) s->state = st + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/.cvsignore b/tools/.cvsignore new file mode 100644 index 00000000..50880759 --- /dev/null +++ b/tools/.cvsignore @@ -0,0 +1,3 @@ +jabberd +Makefile +Makefile.in diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 00000000..40326478 --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,17 @@ +bin_SCRIPTS = jabberd +EXTRA_DIST = db-setup.mysql db-setup.pgsql jabberd.in jabberd.rc pipe-auth.pl migrate.pl db-update.mysql db-setup.oracle + +edit = sed \ + -e 's,@sysconfdir\@,$(sysconfdir),g' \ + -e 's,@VERSION\@,$(VERSION),g' \ + -e 's,@bindir\@,$(bindir),g' + +$(bin_SCRIPTS): $(EXTRA_DIST) + @echo "generating $@ from $@.in"; \ + rm -f $@ $@.tmp; \ + $(edit) < $@.in > $@.tmp; \ + chmod +x $@.tmp; \ + mv $@.tmp $@ + +clean-local: + rm -f $(bin_SCRIPTS) diff --git a/tools/db-setup.mysql b/tools/db-setup.mysql new file mode 100644 index 00000000..dd5f2317 --- /dev/null +++ b/tools/db-setup.mysql @@ -0,0 +1,174 @@ +-- +-- This is the required schema for MySQL. Load this into the database +-- using the mysql interactive terminal: +-- +-- mysql> \. db-setup.mysql +-- + +CREATE DATABASE jabberd2; +USE jabberd2; + +-- +-- c2s authentication/registration table +-- +CREATE TABLE `authreg` ( + `username` TEXT, KEY `username` (`username`(255)), + `realm` TINYTEXT, KEY `realm` (`realm`(255)), + `password` TINYTEXT, + `token` VARCHAR(10), + `sequence` INT, + `hash` VARCHAR(40) ); + +-- +-- Session manager tables +-- + +-- +-- Active (seen) users +-- Used by: core +-- +CREATE TABLE `active` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `time` INT ); + +-- +-- Logout times +-- Used by: mod_iq_last +-- +CREATE TABLE `logout` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `time` INT ); + +-- +-- Roster items +-- Used by: mod_roster +-- +CREATE TABLE `roster-items` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `jid` TEXT, + `name` TEXT, + `to` TINYINT, + `from` TINYINT, + `ask` INT ); + +-- +-- Roster groups +-- Used by: mod_roster +-- +CREATE TABLE `roster-groups` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `jid` TEXT, + `group` TEXT ); + +-- +-- vCard (user profile information) +-- Used by: mod_iq_vcard +-- +CREATE TABLE `vcard` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `fn` TEXT, + `nickname` TEXT, + `url` TEXT, + `tel` TEXT, + `email` TEXT, + `title` TEXT, + `role` TEXT, + `bday` TEXT, + `desc` TEXT, + `n-given` TEXT, + `n-family` TEXT, + `adr-street` TEXT, + `adr-extadd` TEXT, + `adr-locality` TEXT, + `adr-region` TEXT, + `adr-pcode` TEXT, + `adr-country` TEXT, + `org-orgname` TEXT, + `org-orgunit` TEXT ); + +-- +-- Offline message queue +-- Used by: mod_offline +-- +CREATE TABLE `queue` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `xml` MEDIUMTEXT ); + +-- +-- Private XML storage +-- Used by: mod_iq_private +-- +CREATE TABLE `private` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `ns` TEXT, + `xml` MEDIUMTEXT ); + +-- +-- Message Of The Day (MOTD) messages (announcements) +-- Used by: mod_announce +-- +CREATE TABLE `motd-message` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `xml` TEXT ); + +-- +-- Times of last MOTD message for each user +-- Used by: mod_announce +-- +CREATE TABLE `motd-times` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `time` INT ); + +-- +-- User-published discovery items +-- Used by: mod_disco_publish +-- +CREATE TABLE `disco-items` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `jid` TEXT, + `name` TEXT, + `node` TEXT ); + +-- +-- Default privacy list +-- Used by: mod_privacy +-- +CREATE TABLE `privacy-default` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `default` text ); + +-- +-- Privacy lists +-- Used by: mod_privacy +-- +CREATE TABLE `privacy-items` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `list` TEXT, + `type` TEXT, + `value` TEXT, + `deny` TINYINT, + `order` INT, + `block` INT ); + +-- +-- Vacation settings +-- Used by: mod_vacation +-- +CREATE TABLE `vacation-settings` ( + `collection-owner` TEXT NOT NULL, KEY(`collection-owner`(255)), + `object-sequence` BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY(`object-sequence`), + `start` INT, + `end` INT, + `message` TEXT ); diff --git a/tools/db-setup.oracle b/tools/db-setup.oracle new file mode 100644 index 00000000..06ffdc82 --- /dev/null +++ b/tools/db-setup.oracle @@ -0,0 +1,411 @@ +/************************************************ + * This is the schema for Oracle. + * + * The following code is used to remove the tables: + * + * DROP TABLE "authreg" CASCADE CONSTRAINTS; + * DROP TABLE "active" CASCADE CONSTRAINTS; + * DROP TABLE "logout" CASCADE CONSTRAINTS; + * DROP TABLE "roster-items" CASCADE CONSTRAINTS; + * DROP TABLE "roster-groups" CASCADE CONSTRAINTS; + * DROP TABLE "vcard" CASCADE CONSTRAINTS; + * DROP TABLE "queue" CASCADE CONSTRAINTS; + * DROP TABLE "private" CASCADE CONSTRAINTS; + * DROP TABLE "motd-message" CASCADE CONSTRAINTS; + * DROP TABLE "motd-times" CASCADE CONSTRAINTS; + * DROP TABLE "disco-items" CASCADE CONSTRAINTS; + * DROP TABLE "privacy-default" CASCADE CONSTRAINTS; + * DROP TABLE "privacy-items" CASCADE CONSTRAINTS; + * DROP TABLE "vacation-settings" CASCADE CONSTRAINTS; + * DROP SEQUENCE "seq-active"; + * DROP SEQUENCE "seq-logout"; + * DROP SEQUENCE "seq-roster-items"; + * DROP SEQUENCE "seq-roster-groups"; + * DROP SEQUENCE "seq-vcard"; + * DROP SEQUENCE "seq-queue"; + * DROP SEQUENCE "seq-private"; + * DROP SEQUENCE "seq-motd-message"; + * DROP SEQUENCE "seq-motd-times"; + * DROP SEQUENCE "seq-disco-items"; + * DROP SEQUENCE "seq-privacy-default"; + * DROP SEQUENCE "seq-privacy-items"; + * DROP SEQUENCE "seq-vacation-settings"; + */ + +CREATE TABLE "authreg" ( + "username" varchar2(256), + "realm" varchar2(256), + "password" varchar2(256), + "token" varchar2(10), + "sequence" number, + "hash" varchar2(40) ); + +CREATE SEQUENCE "seq-active" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-logout" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-roster-items" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-roster-groups" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-vcard" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-queue" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-private" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-motd-message" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-motd-times" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-disco-items" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-privacy-default" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-privacy-items" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; +CREATE SEQUENCE "seq-vacation-settings" INCREMENT BY 1 START WITH 1 MINVALUE 1 NOCYCLE NOCACHE NOORDER; + +/* + * Session manager tables + * + * + * Active (seen) users + * Used by: core + */ +CREATE TABLE "active" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "time" number ); + +CREATE OR REPLACE TRIGGER "active-object-sequence" +BEFORE INSERT +ON "active" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-active".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +ALTER TABLE "active" ADD ( + PRIMARY KEY ("collection-owner")); + +/* + * Logout times + * Used by: mod_iq_last + */ +CREATE TABLE "logout" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "time" number ); + +CREATE OR REPLACE TRIGGER "logout-object-sequence" +BEFORE INSERT +ON "logout" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-logout".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +ALTER TABLE "logout" ADD ( + PRIMARY KEY ("collection-owner")); + +/* + * Roster items + * Used by: mod_roster + */ +CREATE TABLE "roster-items" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "jid" varchar2(4000), + "name" varchar2(4000), + "to" char(1), + "from" char(1), + "ask" number ); + +CREATE OR REPLACE TRIGGER "roster-items-object-sequence" +BEFORE INSERT +ON "roster-items" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-roster-items".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +CREATE INDEX "roster-items-collection-owner" + ON "roster-items"("collection-owner"); + +CREATE INDEX "roster-items-jid" + ON "roster-items"("jid"); + +/* + * Roster groups + * Used by: mod_roster + */ +CREATE TABLE "roster-groups" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "jid" varchar2(4000), + "group" varchar2(4000) ); + +CREATE OR REPLACE TRIGGER "roster-groups-object-sequence" +BEFORE INSERT +ON "roster-groups" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-roster-groups".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +CREATE INDEX "roster-groups-collection-owner" + ON "roster-groups"("collection-owner"); + +CREATE INDEX "roster-groups-jid" + ON "roster-groups"("jid"); + +/* + * vCard (user profile information) + * Used by: mod_iq_vcard + */ +CREATE TABLE "vcard" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "fn" varchar2(4000), + "nickname" varchar2(4000), + "url" varchar2(4000), + "tel" varchar2(4000), + "email" varchar2(4000), + "title" varchar2(4000), + "role" varchar2(4000), + "bday" varchar2(4000), + "desc" varchar2(4000), + "n-given" varchar2(4000), + "n-family" varchar2(4000), + "adr-street" varchar2(4000), + "adr-extadd" varchar2(4000), + "adr-locality" varchar2(4000), + "adr-region" varchar2(4000), + "adr-pcode" varchar2(4000), + "adr-country" varchar2(4000), + "org-orgname" varchar2(4000), + "org-orgunit" varchar2(4000) ); + +CREATE OR REPLACE TRIGGER "vcard-object-sequence" +BEFORE INSERT +ON "vcard" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-vcard".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +CREATE INDEX "vcard-collection-owner" + ON "vcard"("collection-owner"); + +/* + * Offline message queue + * Used by: mod_offline + */ +CREATE TABLE "queue" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "xml" varchar2(4000) ); + +CREATE OR REPLACE TRIGGER "queue-object-sequence" +BEFORE INSERT +ON "queue" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-queue".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +CREATE INDEX "queue-collection-owner" + ON "queue"("collection-owner"); + +/* + * Private XML storage + * Used by: mod_iq_private + */ +CREATE TABLE "private" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "ns" varchar2(4000), + "xml" varchar2(4000) ); + +CREATE OR REPLACE TRIGGER "private-object-sequence" +BEFORE INSERT +ON "private" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-private".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +CREATE INDEX "private-collection-owner" + ON "private"("collection-owner"); + +/* + * Message Of The Day (MOTD) messages (announcements) + * Used by: mod_announce + */ +CREATE TABLE "motd-message" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "xml" varchar2(4000) ); + +CREATE OR REPLACE TRIGGER "motd-message-object-sequence" +BEFORE INSERT +ON "motd-message" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-motd-message".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +ALTER TABLE "motd-message" ADD ( + PRIMARY KEY ("collection-owner")); + +/* + * Times of last MOTD message for each user + * Used by: mod_announce + */ +CREATE TABLE "motd-times" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "time" number ); + +CREATE OR REPLACE TRIGGER "motd-times-object-sequence" +BEFORE INSERT +ON "motd-times" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-motd-times".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +ALTER TABLE "motd-times" ADD ( + PRIMARY KEY ("collection-owner")); + +/* + * User-published discovery items + * Used by: mod_disco_publish + */ +CREATE TABLE "disco-items" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "jid" varchar2(4000), + "name" varchar2(4000), + "node" varchar2(4000) ); + +CREATE OR REPLACE TRIGGER "disco-items-object-sequence" +BEFORE INSERT +ON "disco-items" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-disco-items".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +CREATE INDEX "disco-items-collection-owner" + ON "disco-items"("collection-owner"); + +/* + * Default privacy list + * Used by: mod_privacy + */ +CREATE TABLE "privacy-default" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "default" varchar2(4000) ); + +CREATE OR REPLACE TRIGGER "privacy-default-object-seq" +BEFORE INSERT +ON "privacy-default" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-privacy-default".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +ALTER TABLE "privacy-default" ADD ( + PRIMARY KEY ("collection-owner")); + +/* + * Privacy lists + * Used by: mod_privacy + */ +CREATE TABLE "privacy-items" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "list" varchar2(4000), + "type" varchar2(4000), + "value" varchar2(4000), + "deny" char(1), + "order" number, + "block" number ); + +CREATE OR REPLACE TRIGGER "privacy-items-object-sequence" +BEFORE INSERT +ON "privacy-items" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-privacy-items".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +CREATE INDEX "privacy-items-collection-owner" + ON "privacy-items"("collection-owner"); + +/* + * Vacation settings + * Used by: mod_vacation + */ +CREATE TABLE "vacation-settings" ( + "collection-owner" varchar2(4000), + "object-sequence" number, + "start" number, + "end" number, + "message" varchar2(4000) ); + +CREATE OR REPLACE TRIGGER "vacation-settings-object-seq" +BEFORE INSERT +ON "vacation-settings" +FOR EACH ROW +BEGIN + IF :NEW."object-sequence" IS NULL THEN + SELECT "seq-vacation-settings".NextVal INTO :NEW."object-sequence" FROM dual; + END IF; +END; +/ +SHOW ERRORS; + +ALTER TABLE "vacation-settings" ADD ( + PRIMARY KEY ("collection-owner")); + + diff --git a/tools/db-setup.pgsql b/tools/db-setup.pgsql new file mode 100644 index 00000000..0b9ca20c --- /dev/null +++ b/tools/db-setup.pgsql @@ -0,0 +1,176 @@ +-- +-- This is the required schema for PostgreSQL. Load this into the +-- database using the psql interactive terminal: +-- +-- template1=> \i db-setup.pgsql +-- + +CREATE DATABASE jabberd2; +\c jabberd2 + +-- +-- c2s authentication/registration table +-- +CREATE TABLE "authreg" ( + "username" varchar(256), + "realm" varchar(256), + "password" varchar(256), + "token" varchar(10), + "sequence" integer, + "hash" varchar(40) ); + +CREATE SEQUENCE "object-sequence"; + +-- +-- Session manager tables +-- + +-- +-- Active (seen) users +-- Used by: core +-- +CREATE TABLE "active" ( + "collection-owner" text PRIMARY KEY, + "object-sequence" bigint, + "time" integer ); + +-- +-- Logout times +-- Used by: mod_iq_last +-- +CREATE TABLE "logout" ( + "collection-owner" text PRIMARY KEY, + "object-sequence" bigint, + "time" integer ); + +-- +-- Roster items +-- Used by: mod_roster +-- +CREATE TABLE "roster-items" ( + "collection-owner" text, + "object-sequence" bigint, + "jid" text, + "name" text, + "to" boolean, + "from" boolean, + "ask" integer ); + +-- +-- Roster groups +-- Used by: mod_roster +-- +CREATE TABLE "roster-groups" ( + "collection-owner" text, + "object-sequence" bigint, + "jid" text, + "group" text ); + +-- +-- vCard (user profile information) +-- Used by: mod_iq_vcard +-- +CREATE TABLE "vcard" ( + "collection-owner" text, + "object-sequence" bigint, + "fn" text, + "nickname" text, + "url" text, + "tel" text, + "email" text, + "title" text, + "role" text, + "bday" text, + "desc" text, + "n-given" text, + "n-family" text, + "adr-street" text, + "adr-extadd" text, + "adr-locality" text, + "adr-region" text, + "adr-pcode" text, + "adr-country" text, + "org-orgname" text, + "org-orgunit" text ); + +-- +-- Offline message queue +-- Used by: mod_offline +-- +CREATE TABLE "queue" ( + "collection-owner" text, + "object-sequence" bigint, + "xml" text ); + +-- +-- Private XML storage +-- Used by: mod_iq_private +-- +CREATE TABLE "private" ( + "collection-owner" text, + "object-sequence" bigint, + "ns" text, + "xml" text ); + +-- +-- Message Of The Day (MOTD) messages (announcements) +-- Used by: mod_announce +-- +CREATE TABLE "motd-message" ( + "collection-owner" text PRIMARY KEY, + "object-sequence" bigint, + "xml" text ); + +-- +-- Times of last MOTD message for each user +-- Used by: mod_announce +-- +CREATE TABLE "motd-times" ( + "collection-owner" text PRIMARY KEY, + "object-sequence" bigint, + "time" integer ); + +-- +-- User-published discovery items +-- Used by: mod_disco_publish +-- +CREATE TABLE "disco-items" ( + "collection-owner" text, + "object-sequence" bigint, + "jid" text, + "name" text, + "node" text ); + +-- +-- Default privacy list +-- Used by: mod_privacy +-- +CREATE TABLE "privacy-default" ( + "collection-owner" text PRIMARY KEY, + "object-sequence" bigint, + "default" text ); + +-- +-- Privacy lists +-- Used by: mod_privacy +-- +CREATE TABLE "privacy-items" ( + "collection-owner" text, + "object-sequence" bigint, + "list" text, + "type" text, + "value" text, + "deny" boolean, + "order" integer, + "block" integer ); + +-- +-- Vacation settings +-- Used by: mod_vacation +-- +CREATE TABLE "vacation-settings" ( + "collection-owner" text PRIMARY KEY, + "object-sequence" bigint, + "start" int, + "end" int, + "message" text ); diff --git a/tools/db-setup.sqlite b/tools/db-setup.sqlite new file mode 100644 index 00000000..3d2b6699 --- /dev/null +++ b/tools/db-setup.sqlite @@ -0,0 +1,170 @@ +-- +-- This is the required schema for sqlite. +-- +-- sqlite3 jabberd2.db < db-setup.sqlite +-- + +-- +-- c2s authentication/registration table +-- +CREATE TABLE 'authreg' ( + 'username' TEXT, + 'realm' TEXT, + 'password' TEXT, + 'token' VARCHAR(10), + 'sequence' INTEGER, + 'hash' VARCHAR(40) ); + +-- +-- Session manager tables +-- + +-- +-- Active (seen) users +-- Used by: core +-- +CREATE TABLE 'active' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'time' INT ); + +-- +-- Logout times +-- Used by: mod_iq_last +-- +CREATE TABLE 'logout' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'time' INT ); + +-- +-- Roster items +-- Used by: mod_roster +-- +CREATE TABLE 'roster-items' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'jid' TEXT, + 'name' TEXT, + 'to' BOOL, + 'from' BOOL, + 'ask' INTEGER ); + +-- +-- Roster groups +-- Used by: mod_roster +-- +CREATE TABLE 'roster-groups' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'jid' TEXT, + 'group' TEXT ); + +-- +-- vCard (user profile information) +-- Used by: mod_iq_vcard +-- +CREATE TABLE 'vcard' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'fn' TEXT, + 'nickname' TEXT, + 'url' TEXT, + 'tel' TEXT, + 'email' TEXT, + 'title' TEXT, + 'role' TEXT, + 'bday' TEXT, + 'desc' TEXT, + 'n-given' TEXT, + 'n-family' TEXT, + 'adr-street' TEXT, + 'adr-extadd' TEXT, + 'adr-locality' TEXT, + 'adr-region' TEXT, + 'adr-pcode' TEXT, + 'adr-country' TEXT, + 'org-orgname' TEXT, + 'org-orgunit' TEXT ); + +-- +-- Offline message queue +-- Used by: mod_offline +-- +CREATE TABLE 'queue' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'xml' TEXT ); + +-- +-- Private XML storage +-- Used by: mod_iq_private +-- +CREATE TABLE 'private' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'ns' TEXT, + 'xml' TEXT ); + +-- +-- Message Of The Day (MOTD) messages (announcements) +-- Used by: mod_announce +-- +CREATE TABLE 'motd-message' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'xml' TEXT ); + +-- +-- Times of last MOTD message for each user +-- Used by: mod_announce +-- +CREATE TABLE 'motd-times' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'time' INTEGER ); + +-- +-- User-published discovery items +-- Used by: mod_disco_publish +-- +CREATE TABLE 'disco-items' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'jid' TEXT, + 'name' TEXT, + 'node' TEXT ); + +-- +-- Default privacy list +-- Used by: mod_privacy +-- +CREATE TABLE 'privacy-default' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'default' TEXT ); + +-- +-- Privacy lists +-- Used by: mod_privacy +-- +CREATE TABLE 'privacy-items' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'list' TEXT, + 'type' TEXT, + 'value' TEXT, + 'deny' BOOL, + 'order' INTEGER, + 'block' INTEGER ); + +-- +-- Vacation settings +-- Used by: mod_vacation +-- +CREATE TABLE 'vacation-settings' ( + 'collection-owner' TEXT NOT NULL, + 'object-sequence' INTEGER PRIMARY KEY, + 'start' INTEGER, + 'end' INTEGER, + 'message' TEXT ); diff --git a/tools/db-update.mysql b/tools/db-update.mysql new file mode 100644 index 00000000..9138e850 --- /dev/null +++ b/tools/db-update.mysql @@ -0,0 +1,72 @@ +-- +-- This updates and creates indexes for jabberd2 mysql databases created prior to 2.0s6. +-- Run this using the mysql interactive terminal: +-- +-- mysql> \. db-update.mysql +-- + +USE jabberd2; + +-- Change the primary keys on collection-owner to normal indexes so as not to +-- enforce uniqueness on the first 255 chars of otherwise different JIDs + +ALTER TABLE `active` DROP PRIMARY KEY; +ALTER TABLE `active` ADD INDEX ( `collection-owner` ( 255 ) ); + +ALTER TABLE `logout` DROP PRIMARY KEY; +ALTER TABLE `logout` ADD INDEX ( `collection-owner` ( 255 ) ); + +ALTER TABLE `vcard` DROP PRIMARY KEY; +ALTER TABLE `vcard` ADD INDEX ( `collection-owner` ( 255 ) ); + +ALTER TABLE `motd-message` DROP PRIMARY KEY; +ALTER TABLE `motd-message` ADD INDEX ( `collection-owner` ( 255 ) ); + +ALTER TABLE `motd-times` DROP PRIMARY KEY; +ALTER TABLE `motd-times` ADD INDEX ( `collection-owner` ( 255 ) ); + +ALTER TABLE `privacy-default` DROP PRIMARY KEY; +ALTER TABLE `privacy-default` ADD INDEX ( `collection-owner` ( 255 ) ); + +-- Add indexes on collection-owner for tables that should have them + +ALTER TABLE `disco-items` ADD INDEX ( `collection-owner` ( 255 ) ); +ALTER TABLE `roster-items` ADD INDEX ( `collection-owner` ( 255 ) ); +ALTER TABLE `roster-groups` ADD INDEX ( `collection-owner` ( 255 ) ); +ALTER TABLE `privacy-items` ADD INDEX ( `collection-owner` ( 255 ) ); +ALTER TABLE `private` ADD INDEX ( `collection-owner` ( 255 ) ); +ALTER TABLE `queue` ADD INDEX ( `collection-owner` ( 255 ) ); +ALTER TABLE `vacation-settings` ADD INDEX ( `collection-owner` ( 255 ) ); + +-- Add indexes on username and realm for authreg + +ALTER TABLE `authreg` ADD INDEX ( `username` ( 255 ) ); +ALTER TABLE `authreg` ADD INDEX ( `realm` ( 255 ) ); + +-- Change the field type of xml in queue and private to allow storage > 64K +-- (MEDIUMTEXT will allow up to 16M) + +ALTER TABLE `queue` CHANGE `xml` `xml` MEDIUMTEXT DEFAULT NULL; +ALTER TABLE `private` CHANGE `xml` `xml` MEDIUMTEXT DEFAULT NULL; + +-- Remove 256-char limit on username in authreg table + +ALTER TABLE `authreg` CHANGE `username` `username` TEXT DEFAULT NULL; + +--- Change keys on object-sequence to primary keys - note that mysql's index +--- limit of 255 characters prevents the possibility of using collection-owner +--- as the primary key (as a JID can be longer than that) + +ALTER TABLE `active` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `disco-items` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `logout` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `motd-message` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `motd-times` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `privacy-default` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `privacy-items` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `private` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `queue` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `roster-groups` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `roster-items` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `vacation-settings` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); +ALTER TABLE `vcard` DROP INDEX `object-sequence` , ADD PRIMARY KEY ( `object-sequence` ); diff --git a/tools/jabberd.in b/tools/jabberd.in new file mode 100755 index 00000000..9a896521 --- /dev/null +++ b/tools/jabberd.in @@ -0,0 +1,323 @@ +#!/bin/sh +#-*-Perl-*- + +exec perl -w -x $0 "$@" + +#!perl + +############################################################################## +# +# jabberd - perl wrapper script to manage launching and controlling the various +# binaries that make up the 2.0 version of the jabberd server. +# +############################################################################## + +use strict; +use Getopt::Std; +#use FindBin qw($Bin); +use FileHandle; +use IPC::Open3; +use IO::Select; +use POSIX; +use POSIX qw(setsid); + + +#----------------------------------------------------------------------------- +# Define some initial variables and default them as needed. +#----------------------------------------------------------------------------- +my $Bin = "@bindir@"; +my $VERSION = "@VERSION@"; +my $config_dir = "@sysconfdir@"; +my $config = $config_dir."/jabberd.cfg"; +$config = "internal" unless (-e $config); +my $debug = 0; +my $daemon = 0; +my $select = IO::Select->new(); +my ($exe) = ($0 =~ /([^\/]+)$/); +my %jobs; +my %fhs; +my @programs; + + +#----------------------------------------------------------------------------- +# Process the command line arguments +#----------------------------------------------------------------------------- +my %opts; +getopts("c:Dhb",\%opts); +&usage if exists($opts{h}); +if (exists($opts{c})) +{ + $config = $opts{c} if (defined($opts{c}) && ($opts{c} ne "")); + &usage() if (!defined($opts{c}) || ($opts{c} eq "")); +} + +$debug = 1 if exists($opts{D}); +$daemon = 1 if exists($opts{b}); + +#----------------------------------------------------------------------------- +# Catch some signals so that we can handle them later. +#----------------------------------------------------------------------------- +$SIG{HUP} = \&Signal; +$SIG{TERM} = \&Signal; +$SIG{INT} = \&Signal; +$SIG{CHLD} = "IGNORE"; + + +#----------------------------------------------------------------------------- +# Setup the jobs: router, resolver, sm, c2s, s2s +#----------------------------------------------------------------------------- +$jobs{jabberd}->{prefix} = "JBRD"; + +$jobs{router}->{cmd} = "$Bin/router"; +$jobs{router}->{config} = "$config_dir/router.xml"; +$jobs{router}->{prefix} = "ROUT"; + +$jobs{resolver}->{cmd} = "$Bin/resolver"; +$jobs{resolver}->{config} = "$config_dir/resolver.xml"; +$jobs{resolver}->{prefix} = "RSLV"; + +$jobs{sm}->{cmd} = "$Bin/sm"; +$jobs{sm}->{config} = "$config_dir/sm.xml"; +$jobs{sm}->{prefix} = "SM"; + +$jobs{c2s}->{cmd} = "$Bin/c2s"; +$jobs{c2s}->{config} = "$config_dir/c2s.xml"; +$jobs{c2s}->{prefix} = "C2S"; + +$jobs{s2s}->{cmd} = "$Bin/s2s"; +$jobs{s2s}->{config} = "$config_dir/s2s.xml"; +$jobs{s2s}->{prefix} = "S2S"; + +if ($config eq "internal") +{ + $programs[0] = ["router"]; + $programs[1] = ["resolver"]; + $programs[2] = ["sm"]; + $programs[3] = ["c2s"]; + $programs[4] = ["s2s"]; +} +else +{ + if (!(-f $config)) + { + print "ERROR: config file does not exist: $config\n"; + exit(1); + } + open(CFG,$config); + while() + { + next if /^\#/; + next if /^\s*$/; + my ($job,$config) = /^\s*(\S+)\s*(\S*)\s*$/; + # Assume that all the commands are in the same directory + # as the jabberd script. The current configuration file + # format does not allow specification of pathnames for commands. + my $cmd = "$Bin/$job"; + push(@programs,[$job,$config,$cmd]); + } + close(CFG); +} + +if ($debug) +{ + &debug("jabberd","stdout","debug on\n"); + &debug("jabberd","stdout","version($VERSION)\n"); + &debug("jabberd","stdout","config_dir($config_dir)\n"); +} + +#----------------------------------------------------------------------------- +# Launch all of the jobs. +#----------------------------------------------------------------------------- +if ($#programs == -1) +{ + print "ERROR: No jobs to launch.\n"; + exit(1); +} + + +foreach my $job (@programs) +{ + &LaunchJob($job->[0],$job->[1],$job->[2]); +} + +unless (!$daemon || $debug) +{ + # Fork and become a daemon. Exit if we are the parent process. + defined(my $pid = fork()) || die "Could not fork: $!"; + POSIX:_exit(0) if $pid; + # If we are the child process, continue (but act like a daemon). + setsid or die "Could not start a new POSIX Session: $!"; + chdir "/" or die "Could not chdir to /: $!"; + umask 0; + open STDIN, "/dev/null" or die "Could not set STDIN to /dev/null: $!"; + open STDOUT, "/dev/null" or die "Could not set STDOUT to /dev/null: $!"; + open STDERR, "/dev/null" or die "Could not set STDERR to /dev/null: $!"; +} + +#----------------------------------------------------------------------------- +# Run the main loop. Read the output from the jobs, watch for dead jobs and +# restart them, make sure that the debug output is clearly marked. +#----------------------------------------------------------------------------- +while (1) +{ + my @ready = $select->can_read(0); + foreach my $fh (@ready) + { + my $line = <$fh>; + if (defined($line)) + { + &debug($fhs{$fh}->{job},$fhs{$fh}->{std},$line); + } + else + { + print "ERROR: $fhs{$fh}->{job} died. Shutting down server.\n"; + &Signal("TERM"); + } + } + + select(undef,undef,undef,.01); +} + + + +############################################################################## +# +# LaunchJob - Do all of the necessary steps to monitor the job and launch it. +# +############################################################################## +sub LaunchJob +{ + my $job = shift; + my $config = shift; + my $cmd = shift; + + if (!defined($cmd)) + { + $cmd = $jobs{$job}->{cmd}; + } + if (defined($config)) + { + $cmd .= " -c ".$config; + } + else + { + $cmd .= " -c ".$jobs{$job}->{config}; + } + $cmd .= " -D" if $debug; + + &debug("jabberd","stdout","LaunchJob: $job -> $cmd\n"); + + &CloseJob($job) if exists($jobs{$job}->{launched}); + + $jobs{$job}->{stdout} = new FileHandle(); + $jobs{$job}->{stderr} = new FileHandle(); + + $jobs{$job}->{stdout}->autoflush(1); + $jobs{$job}->{stderr}->autoflush(1); + + my $stdin = new FileHandle(); + $jobs{$job}->{pid} = open3($stdin, + $jobs{$job}->{stdout}, + $jobs{$job}->{stderr}, + $cmd); + + print $stdin "\n"; + $stdin->close(); + + $select->add($jobs{$job}->{stdout}); + $select->add($jobs{$job}->{stderr}); + + $jobs{$job}->{launched} = 1; + + $fhs{$jobs{$job}->{stdout}}->{job} = $job; + $fhs{$jobs{$job}->{stdout}}->{std} = "stdout"; + + $fhs{$jobs{$job}->{stderr}}->{job} = $job; + $fhs{$jobs{$job}->{stderr}}->{std} = "stderr"; +} + + +############################################################################## +# +# CloseJob - Do all of the necessary steps to clean up after a job. +# +############################################################################## +sub CloseJob +{ + my $job = shift; + + $select->remove($jobs{$job}->{stdout}, + $jobs{$job}->{stderr}); + + $jobs{$job}->{stdout}->close(); + $jobs{$job}->{stderr}->close(); + + delete($jobs{$job}->{launched}); +} + + +############################################################################## +# +# Signal - when we get a signal... we need to do something about it. +# +############################################################################## +sub Signal +{ + my $sig = shift; + + &debug("jabberd","stdout","Got a signal... pass it on.\n"); + + foreach my $job (keys(%jobs)) + { + next unless exists($jobs{$job}->{launched}); + kill $sig => $jobs{$job}->{pid}; + } + + if (($sig eq "INT") || ($sig eq "TERM")) + { + &debug("jabberd","stdout","It was a $sig. Shut it all down!\n"); + exit(0); + } +} + + +############################################################################## +# +# debug - print out a message for debug. Making sure that the prefix is the +# program that generated the debug. +# +############################################################################## +sub debug +{ + return unless $debug; + my $job = shift; + my $std = shift; + + #my $flag = " "; + #$flag = "*" if ($std eq "stderr"); + #printf("%s%-4s: ",$flag,$jobs{$job}->{prefix}); + + my $prefix = $jobs{$job}->{prefix}; + $prefix = $job if ! defined($prefix); + printf("%-4s: ",$prefix); + print join("",@_); +} + + +############################################################################## +# +# usage - print out the help and exit +# +############################################################################## +sub usage +{ + print "$exe - jabberd wrapper script ($VERSION)\n"; + print "Usage: $exe \n"; + print "Options are:\n"; + print " -c config file to use [default: $config]\n"; + print " -D Show debug output\n"; + print " -b Push into background\n"; + print " -h Show this help\n"; + exit(0); +} + diff --git a/tools/jabberd.rc b/tools/jabberd.rc new file mode 100644 index 00000000..71140476 --- /dev/null +++ b/tools/jabberd.rc @@ -0,0 +1,181 @@ +#!/bin/bash +# +# Raymond 25DEC2003 support@bigriverinfotech.com +# /etc/rc.d/init.d/jabberd2 +# init script for jabberd2 processes +# Tested under jabberd-2.0rc2 and Fedora 1.0 only +# +# processname: jabberd2 +# description: jabberd2 is the next generation of the jabberd server +# chkconfig: 2345 85 15 +# +if [ -f /etc/init.d/functions ]; then + . /etc/init.d/functions +elif [ -f /etc/rc.d/init.d/functions ]; then + . /etc/rc.d/init.d/functions +else + echo -e "\ajabberd2: unable to locate functions lib. Cannot continue." + exit -1 +fi +# +progs="router resolver sm c2s s2s" +progsPath="/usr/local/bin" +confPath="/usr/local/etc/jabberd" +pidPath="/usr/local/var/jabberd/pid" +statusCol="echo -ne \\033[60G" +statusColorOK="echo -ne \\033[1;32m" +statusColorFailed="echo -ne \\033[1;31m" +statusColorNormal="echo -ne \\033[0;39m" +retval=0 +# +StatusOK ( ) { + ${statusCol} + echo -n "[ " + ${statusColorOK} + echo -n "OK" + ${statusColorNormal} + echo " ]" + return 0 +} +# +StatusFailed ( ) { + echo -ne "\a" + ${statusCol} + echo -n "[" + ${statusColorFailed} + echo -n "FAILED" + ${statusColorNormal} + echo "]" + return 0 +} +# +ReqBins ( ) { + for prog in ${progs}; do + if [ ! -x ${progsPath}/${prog} ]; then + echo -n "jabberd2 binary [${prog}] not found." + StatusFailed + echo "Cannot continue." + return -1 + fi + done + return 0 +} +# +ReqConfs ( ) { + for prog in ${progs}; do + if [ ! -f ${confPath}/${prog}.xml ]; then + echo -n "jabberd2 configuration [${prog}.xml] not found." + StatusFailed + echo "Cannot continue." + return -1 + fi + done + return 0 +} +# +ReqDirs ( ) { + if [ ! -d ${pidPath} ]; then + echo -n "jabberd2 PID directory not found. Cannot continue." + StatusFailed + return -1 + fi + return 0 +} +# +Start ( ) { + for req in ReqBins ReqConfs ReqDirs; do + ${req} + retval=$? + [ ${retval} == 0 ] || return ${retval} + done + echo "Initializing jabberd2 processes ..." + for prog in ${progs}; do + if [ $( pidof -s ${prog} ) ]; then + echo -ne "\tprocess [${prog}] already running" + StatusFailed + sleep 1 + continue + fi + echo -ne "\tStarting ${prog}: " + if [ ${prog} == "router" ]; then + ports="5347" + elif [ ${prog} == "c2s" ]; then + ports="5222 5223" + elif [ ${prog} == "s2s" ]; then + ports="5269" + else + ports="" + fi + for port in ${ports}; do + if [ $( netstat --numeric-ports --listening --protocol=inet | + gawk '{ print $4 }' | + gawk -F : '{ print $NF }' | + grep -c ${port}$ ) -ne "0" ]; then + StatusFailed + echo -e "\tPort ${port} is currently in use. Cannot continue" + echo -e "\tIs a Jabber 1.x server running?" + Stop + let retval=-1 + break 2 + fi + done + rm -f /var/lock/subsys/${prog} + rm -f ${pidPath}/${prog}.pid + args="-c ${confPath}/${prog}.xml" + ${progsPath}/${prog} ${args} & 2> /dev/null + retval=$? + if [ ${retval} == 0 ]; then + StatusOK + touch /var/lock/subsys/${prog} + else + StatusFailed + Stop + let retval=-1 + break + fi + sleep 1 + done + return ${retval} +} +# +Stop ( ) { + echo "Terminating jabberd2 processes ..." + for prog in ${progs}; do + echo -ne "\tStopping ${prog}: " + killproc ${prog} + retval=$? + if [ ${retval} == 0 ]; then + rm -f /var/lock/subsys/${prog} + rm -f ${pidPath}/${prog}.pid + fi + echo + sleep 1 + done + return ${retval} +} +# +case "$1" in + start) + Start + ;; + stop) + Stop + ;; + restart) + Stop + Start + ;; + condrestart) + if [ -f /var/lock/subsys/${prog} ]; then + Stop + sleep 3 + Start + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|condrestart}" + let retval=-1 +esac +exit ${retval} +# +# eof diff --git a/tools/migrate.pl b/tools/migrate.pl new file mode 100755 index 00000000..8a6d7ca8 --- /dev/null +++ b/tools/migrate.pl @@ -0,0 +1,376 @@ +#!/usr/bin/perl -w + +# +# jabberd 1.4 -> 2.0 migration tool +# Copyright (c) 2003 Robert Norris +# GPL v2. See http://www.gnu.org/copyleft/gpl.html for more info +# + +# +# This can migrate from any 1.4 XDB source, into 2.0 MySQL or +# PostgreSQL databases. Other 2.0 databases (such as Berkeley) are not +# supported at this time. +# +# Currently, this can migrate authentication information and rosters. +# Anything more than that, you're on your own. +# +# There's very little error checking. If you find some user data that +# consistently breaks, please file a bug report with some sample data. +# + +# +# Installation +# +# 1. Install the following Perl packages +# +# - XML::Stream 1.17 or higher (from JabberStudio) +# - Net::Jabber 1.29 or higher (from JabberStudio) +# - Digest::SHA1 +# - DBI +# - DBD::Pg or DBD::mysql +# +# 2. Make sure the appropriate database schema is imported into your +# database (db-setup.mysql or db-setup.pgsql) +# +# 3. Add something like the following to your 1.4 jabber.xml: +# +# +# +# 127.0.0.1 +# 7000 +# secret +# +# +# +# 4. Create a file with the JIDs of the users you wish to migrate, one +# per line. +# +# 5. Edit the config below to taste. +# +# 6. Run. +# + +# host/port that jabberd 1.4 is listening for this component on +my $COMP_HOST = 'localhost'; +my $COMP_PORT = 7000; +# name of component +my $COMP_NAME = 'migrate'; +# component secret +my $COMP_SECRET = 'secret'; + +# backend database type (either 'mysql' or 'pgsql') +my $DB_TYPE = 'mysql'; +# host/port of the database server +my $DB_HOST = 'localhost'; +my $DB_PORT = 3306; # 5432 is default for pgsql +# database name +my $DB_NAME = 'jabberd2'; +# database user/pass +my $DB_USER = 'jabberd2'; +my $DB_PASS = 'secret'; + +# file containing user list +my $USER_FILE = "migrate-users"; + +# data types to migrate +my @DATA_TYPES = qw(roster active auth); + +# authentication realm for migrated users +my $AUTH_REALM = 'gideon.its.monash.edu.au'; + +# +# you shouldn't need to touch anything below here +# + +use strict; + +use DBI; +use Digest::SHA1 qw(sha1_hex); + +# +# all of this madness is to work around problems and shortcomings with Net::Jabber +# +use Net::Jabber::XDB; +$Net::Jabber::XDB::FUNCTIONS{XDB}->{XPath}->{Type} = 'master'; + +use Net::Jabber 1.29 qw(Component); + +package Net::Jabber::XDB; + +$FUNCTIONS{Data}->{XPath}->{Type} = 'node'; +$FUNCTIONS{Data}->{XPath}->{Path} = '*[@xmlns]'; +$FUNCTIONS{Data}->{XPath}->{Child} = 'Data'; +$FUNCTIONS{Data}->{XPath}->{Calls} = ['Get','Defined']; + +package Net::Jabber::Data; + +$FUNCTIONS{XMLNS}->{XPath}->{Path} = '@xmlns'; + +$FUNCTIONS{Data}->{XPath}->{Type} = 'node'; +$FUNCTIONS{Data}->{XPath}->{Path} = '*[@xmlns]'; +$FUNCTIONS{Data}->{XPath}->{Child} = 'Data'; +$FUNCTIONS{Data}->{XPath}->{Calls} = ['Get','Defined']; + +my $ns; + +$ns = 'jabber:iq:auth'; + +$NAMESPACES{$ns}->{Password}->{XPath}->{Path} = 'text()'; + +$NAMESPACES{$ns}->{Auth}->{XPath}->{Type} = 'master'; + +$ns = 'jabber:iq:register'; + +$NAMESPACES{$ns}->{Register}->{XPath}->{Type} = 'master'; + +$ns = 'jabber:iq:roster'; + +$NAMESPACES{$ns}->{Item}->{XPath}->{Type} = 'node'; +$NAMESPACES{$ns}->{Item}->{XPath}->{Path} = 'item'; +$NAMESPACES{$ns}->{Item}->{XPath}->{Child} = ['Data','__netjabber__:iq:roster:item']; +$NAMESPACES{$ns}->{Item}->{XPath}->{Calls} = ['Add']; + +$NAMESPACES{$ns}->{Items}->{XPath}->{Type} = 'children'; +$NAMESPACES{$ns}->{Items}->{XPath}->{Path} = 'item'; +$NAMESPACES{$ns}->{Items}->{XPath}->{Child} = ['Data','__netjabber__:iq:roster:item']; +$NAMESPACES{$ns}->{Items}->{XPath}->{Calls} = ['Get']; + +$ns = '__netjabber__:iq:roster:item'; + +$NAMESPACES{$ns}->{Ask}->{XPath}->{Path} = '@ask'; + +$NAMESPACES{$ns}->{Group}->{XPath}->{Type} = 'array'; +$NAMESPACES{$ns}->{Group}->{XPath}->{Path} = 'group/text()'; + +$NAMESPACES{$ns}->{JID}->{XPath}->{Type} = 'jid'; +$NAMESPACES{$ns}->{JID}->{XPath}->{Path} = '@jid'; + +$NAMESPACES{$ns}->{Name}->{XPath}->{Path} = '@name'; + +$NAMESPACES{$ns}->{Subscription}->{XPath}->{Path} = '@subscription'; + +$NAMESPACES{$ns}->{Item}->{XPath}->{Type} = 'master'; + +package main; +# +# end madness +# + +$| = 1; + +print "Loading user file\n"; + +open IN, $USER_FILE or die "couldn't open $USER_FILE for reading: $!"; +my @users = grep { chomp } ; +close IN; + +die "unknown database type '$DB_TYPE'" if $DB_TYPE ne 'mysql' and $DB_TYPE ne 'pgsql'; + +print "Connecting to database\n"; + +my $dbh; +eval { + if($DB_TYPE eq 'mysql') { + $dbh = DBI->connect("dbi:mysql:dbname=$DB_NAME;host=$DB_HOST;port=$DB_PORT", $DB_USER, $DB_PASS, { AutoCommit => 0, RaiseError => 1 }); + } else { + $dbh = DBI->connect("dbi:Pg:dbname=$DB_NAME;host=$DB_HOST;port=$DB_PORT", $DB_USER, $DB_PASS, { AutoCommit => 0, RaiseError => 1 }); + } +}; +if($@) { + die "db connect error: $@"; +} + +print "Connecting to jabber server\n"; + +my $c = new Net::Jabber::Component( +# debuglevel => 1, debugfile => 'stdout', debugtime => 0 +); +$c->Connect( + hostname => $COMP_HOST, + port => $COMP_PORT, + secret => $COMP_SECRET, + componentname => $COMP_NAME, + connectiontype => 'accept'); + +$c->Connected or die "$0: connect to jabber server failed"; + +my ($iq, $xdb, $res); + +print scalar @users, " users to migrate\n"; + +foreach my $user (@users) { + print "Converting data for $user...\n"; + + my $data = { }; + for(@DATA_TYPES) { + print " $_\n"; + eval '_migrate_'.$_.'($data, $user)'; + warn "$@" if $@; + } + + print "Writing to database... "; + + eval { + my ($rows, $tables) = (0, 0); + + foreach my $type (keys %$data) { + foreach my $item (@{$data->{$type}}) { + my $sql = 'INSERT INTO ' . _sql_literal($type) . " ( "; + for(keys %$item) { + $sql .= _sql_literal($_) . ', '; + } + $sql =~ s/, $/) VALUES ( /; + for(keys %$item) { + $sql .= $item->{$_} . ', '; + } + $sql =~ s/, $/)/; + + $dbh->do($sql); + + $rows++; + } + + $tables++; + } + + $dbh->commit; + + print "inserted $rows rows into $tables tables.\n"; + }; + if($@) { + warn "db error: $@"; + $dbh->rollback; + } +} + +$dbh->disconnect; + +sub _sql_literal { + my $arg = shift; + return "\"$arg\"" if $DB_TYPE eq 'pgsql'; + return "\`$arg\`"; +} + +sub _xdb_get { + my ($user, $ns) = @_; + + my $xdb = new Net::Jabber::XDB; + $xdb->SetXDB( + to => $user, + from => $COMP_NAME, + type => 'get', + ns => $ns); + + return $c->SendAndReceiveWithID($xdb); +} + +sub _object_quote { + $dbh->quote(shift); +} + +sub _object_new { + my $item; + + $item->{'collection-owner'} = _object_quote(shift); + $item->{'object-sequence'} = "nextval('object-sequence')" if $DB_TYPE eq 'pgsql'; + + return $item; +} + +sub _migrate_roster { + my ($data, $user) = @_; + + my $xdb = _xdb_get($user, 'jabber:iq:roster'); + my $roster = $xdb->GetData or return; + + my @items = $roster->GetItems; + for(@items) { + my $item = _object_new($user); + $item->{'jid'} = _object_quote($_->GetJID); + $item->{'name'} = _object_quote($_->GetName) if $_->GetName; + + my $s10n = $_->GetSubscription; + if(not $s10n or $s10n eq 'none') { + $item->{'to'} = _object_quote('0'); + $item->{'from'} = _object_quote('0'); + } elsif($s10n eq 'both') { + $item->{'to'} = _object_quote('1'); + $item->{'from'} = _object_quote('1'); + } elsif($s10n eq 'to') { + $item->{'to'} = _object_quote('1'); + $item->{'from'} = _object_quote('0'); + } elsif($s10n eq 'from') { + $item->{'to'} = _object_quote('0'); + $item->{'from'} = _object_quote('1'); + } + + my $ask = $_->GetAsk; + if(not $ask) { + $item->{'ask'} = 0; + } elsif($ask eq 'subscribe') { + $item->{'ask'} = 1; + } elsif($ask eq 'unsubscribe') { + $item->{'ask'} = 2; + } + + push @{$data->{'roster-items'}}, $item; + + my $jid = $item->{'jid'}; + + my @groups = $_->GetGroup; + for(@groups) { + my $item = _object_new($user); + $item->{'jid'} = $jid; + $item->{'group'} = _object_quote($_); + + push @{$data->{'roster-groups'}}, $item; + } + } +} + +sub _migrate_active { + my ($data, $user) = @_; + + my $item = _object_new($user); + $item->{'time'} = time(); + + push @{$data->{'active'}}, $item; +} + +sub _migrate_auth { + my ($data, $user) = @_; + + my $xdb = _xdb_get($user, 'jabber:iq:auth'); + my $auth = $xdb->GetData or return; + + my $item; + $user =~ m/^(.*)\@/; + $item->{'username'} = _object_quote($1); + $item->{'realm'} = _object_quote($AUTH_REALM); + + my $pass = $auth->GetPassword; + $item->{'password'} = _object_quote($pass); + + my $seq = 500; + + my $token = sprintf "%X", time(); + my $h = sha1_hex(sha1_hex($pass) . $token); + for(my $i = 0; $i < $seq; $i++) { + $h = sha1_hex($h); + } + + $item->{'token'} = _object_quote($token); + $item->{'sequence'} = $seq; + $item->{'hash'} = _object_quote($h); + + push @{$data->{'authreg'}}, $item; +} + +sub _migrate_vcard { + my ($data, $user) = @_; + + my $xdb = _xdb_get($user, 'vcard-temp'); + my $vcard = $xdb->GetData or return; + + # !!! implement this +} diff --git a/tools/pipe-auth.pl b/tools/pipe-auth.pl new file mode 100755 index 00000000..f15bf8b9 --- /dev/null +++ b/tools/pipe-auth.pl @@ -0,0 +1,136 @@ +#!/usr/bin/perl -w + +# +# Sample pipe authenticator module. You can use this as a basis for your +# own auth/reg module. See docs/dev/c2s-pipe-authenticator for details +# about the protocol. +# +# This code is hereby placed into the public domain. +# + +use strict; + +use MIME::Base64; + +# Flush output immediately. +$| = 1; + +# On startup, we have to inform c2s of the functions we can deal with. USER-EXISTS is not optional. +print "OK USER-EXISTS GET-PASSWORD CHECK-PASSWORD SET-PASSWORD GET-ZEROK SET-ZEROK CREATE-USER DESTROY-USER FREE\n"; + +# Our main loop +my $buf; +while(sysread (STDIN, $buf, 1024) > 0) +{ + my ($cmd, @args) = split ' ', $buf; + $cmd =~ tr/[A-Z]/[a-z]/; + $cmd =~ tr/-/_/; + + eval "print _cmd_$cmd(\@args), '\n'"; +} + +# Determine if the requested user exists. +sub _cmd_user_exists +{ + my ($user, $realm) = @_; + + # !!! return "OK" if user exists; + + return "NO"; +} + +# Retrieve the user's password. +sub _cmd_get_password +{ + my ($user, $realm) = @_; + + # !!! $pass = [password in database]; + # return "NO" if not $pass; + # $encoded_pass = encode_base64($pass); + # return "OK $encoded_pass" if $encoded_pass; + + return "NO"; +} + +# Compare the given password with the stored password. +sub _cmd_check_password +{ + my ($user, $encoded_pass, $realm) = @_; + + # !!! $pass = decode_base64($encoded_pass); + # return "NO" if not $pass; + # $spass = [password in database]; + # return "OK" if $pass eq $spass; + + return "NO"; +} + +# Store the password in the database. +sub _cmd_set_password +{ + my ($user, $encoded_pass, $realm) = @_; + + # !!! $pass = decode_base64($encoded_pass); + # return "NO" if not $pass; + # $fail = [store $pass in database]; + # return "OK" if not $fail; + + return "NO"; +} + +# Retrieve the user's stored zerok data. +sub _cmd_get_zerok +{ + my ($user, $realm) = @_; + + # !!! $hash = [hash in database]; + # $token = [token in database]; + # $sequence = [sequence in database]; + # return "OK $hash $token $sequence" if $hash and $token and $sequence; + + return "NO"; +} + +# Store the user's zerok data. +sub _cmd_set_zerok +{ + my ($user, $hash, $token, $sequence, $realm) = @_; + + # !!! $fail = [store $hash in database]; + # $fail and $fail = [store $token in database]; + # $fail and $fail = [store $sequence in database]; + # return "OK" if not $fail; + + return "NO"; +} + +# Create a user in the database (with no auth credentials). +sub _cmd_create_user +{ + my ($user, $realm) = @_; + + # !!! $fail = [create user in database] + # return "OK" if not $fail; + + return "NO"; +} + +# Delete a user and associated credentials. +sub _cmd_delete_user +{ + my ($user, $realm) = @_; + + # !!! $fail = [delete user in database] + # return "OK" if not $fail; + + return "NO"; +} + +# c2s shutting down, do the same. +sub _cmd_free +{ + # !!! free data + # close database handles + + exit(0); +} diff --git a/util/.cvsignore b/util/.cvsignore new file mode 100644 index 00000000..4e07ea23 --- /dev/null +++ b/util/.cvsignore @@ -0,0 +1,6 @@ +.deps +Makefile +Makefile.in +*.lo +.libs +*.la diff --git a/util/Makefile.am b/util/Makefile.am new file mode 100644 index 00000000..4d6368a9 --- /dev/null +++ b/util/Makefile.am @@ -0,0 +1,6 @@ +noinst_LTLIBRARIES = libutil.la + +noinst_HEADERS = inaddr.h md5.h sha1.h util.h util_compat.h xdata.h + +libutil_la_SOURCES = access.c base64.c config.c datetime.c hex.c inaddr.c jid.c jqueue.c jsignal.c log.c md5.c nad.c pool.c rate.c serial.c sha1.c stanza.c str.c xdata.c xhash.c +libutil_la_LIBADD = @LDFLAGS@ diff --git a/util/access.c b/util/access.c new file mode 100644 index 00000000..900468c7 --- /dev/null +++ b/util/access.c @@ -0,0 +1,247 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* this implements allow/deny filters for IP address */ + +#include "util.h" + +access_t access_new(int order) +{ + access_t access = (access_t) malloc(sizeof(struct access_st)); + memset(access, 0, sizeof(struct access_st)); + + access->order = order; + + return access; +} + +void access_free(access_t access) +{ + if(access->allow != NULL) free(access->allow); + if(access->deny != NULL) free(access->deny); + free(access); +} + +static int _access_calc_netsize(const char *mask, int defaultsize) +{ + struct in_addr legacy_mask; + int netsize; + + if(inet_aton(mask, &legacy_mask)) + { + /* netmask has been given in dotted decimal form */ + int temp = ntohl(legacy_mask.s_addr); + netsize = 32; + + while(netsize && temp%2==0) + { + netsize--; + temp /= 2; + } + } else { + /* numerical netsize */ + netsize = j_atoi(mask, defaultsize); + } + + return netsize; +} + +/** convert a IPv6 mapped IPv4 address to a real IPv4 address */ +static void _access_unmap_v4(struct sockaddr_in6 *src, struct sockaddr_in *dst) +{ + memset(dst, 0, sizeof(struct sockaddr_in)); + dst->sin_family = AF_INET; + dst->sin_addr.s_addr = htonl((((int)src->sin6_addr.s6_addr[12]*256+src->sin6_addr.s6_addr[13])*256+src->sin6_addr.s6_addr[14])*256+(int)src->sin6_addr.s6_addr[15]); +} + +/** check if two ip addresses are within the same subnet */ +static int _access_check_match(struct sockaddr_storage *ip_1, struct sockaddr_storage *ip_2, int netsize) +{ + struct sockaddr_in *sin_1; + struct sockaddr_in *sin_2; + struct sockaddr_in6 *sin6_1; + struct sockaddr_in6 *sin6_2; + int i; + + sin_1 = (struct sockaddr_in *)ip_1; + sin_2 = (struct sockaddr_in *)ip_2; + sin6_1 = (struct sockaddr_in6 *)ip_1; + sin6_2 = (struct sockaddr_in6 *)ip_2; + + /* addresses of different families */ + if(ip_1->ss_family != ip_2->ss_family) + { + /* maybe on of the addresses is just a IPv6 mapped IPv4 address */ + if (ip_1->ss_family == AF_INET && ip_2->ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6_2->sin6_addr)) + { + struct sockaddr_storage t; + struct sockaddr_in *temp; + + temp = (struct sockaddr_in *)&t; + + _access_unmap_v4(sin6_2, temp); + if(netsize>96) + netsize -= 96; + + return _access_check_match(ip_1, &t, netsize); + } + + if (ip_1->ss_family == AF_INET6 && ip_2->ss_family == AF_INET && IN6_IS_ADDR_V4MAPPED(&sin6_1->sin6_addr)) + { + struct sockaddr_storage t; + struct sockaddr_in *temp; + + temp = (struct sockaddr_in *)&t; + + _access_unmap_v4(sin6_1, temp); + if(netsize>96) + netsize -= 96; + + return _access_check_match(&t, ip_2, netsize); + } + + return 0; + } + + /* IPv4? */ + if(ip_1->ss_family == AF_INET) + { + int netmask; + + if(netsize > 32) + netsize = 32; + + netmask = htonl(-1 << (32-netsize)); + + return ((sin_1->sin_addr.s_addr&netmask) == (sin_2->sin_addr.s_addr&netmask)); + } + + /* IPv6? */ + if(ip_1->ss_family == AF_INET6) + { + unsigned char bytemask; + + if(netsize > 128) + netsize = 128; + + for(i=0; isin6_addr.s6_addr[i] != sin6_2->sin6_addr.s6_addr[i]) + return 0; + + if(netsize%8 == 0) + return 1; + + bytemask = 0xff << (8 - netsize%8); + + return ((sin6_1->sin6_addr.s6_addr[i]&bytemask) == (sin6_2->sin6_addr.s6_addr[i]&bytemask)); + } + + /* unknown address family */ + return 0; +} + +int access_allow(access_t access, char *ip, char *mask) +{ + struct sockaddr_storage ip_addr; + int netsize; + + if(j_inet_pton(ip, &ip_addr) <= 0) + return 1; + + netsize = _access_calc_netsize(mask, ip_addr.ss_family==AF_INET ? 32 : 128); + + access->allow = (access_rule_t) realloc(access->allow, sizeof(struct access_rule_st) * (access->nallow + 1)); + + memcpy(&access->allow[access->nallow].ip, &ip_addr, sizeof(ip_addr)); + access->allow[access->nallow].mask = netsize; + + access->nallow++; + + return 0; +} + +int access_deny(access_t access, char *ip, char *mask) +{ + struct sockaddr_storage ip_addr; + int netsize; + + if(j_inet_pton(ip, &ip_addr) <= 0) + return 1; + + netsize = _access_calc_netsize(mask, ip_addr.ss_family==AF_INET ? 32 : 128); + + access->deny = (access_rule_t) realloc(access->deny, sizeof(struct access_rule_st) * (access->ndeny + 1)); + + memcpy(&access->deny[access->ndeny].ip, &ip_addr, sizeof(ip_addr)); + access->deny[access->ndeny].mask = netsize; + + access->ndeny++; + + return 0; +} + +int access_check(access_t access, char *ip) +{ + struct sockaddr_storage addr; + access_rule_t rule; + int i, allow = 0, deny = 0; + + if(j_inet_pton(ip, &addr) <= 0) + return 0; + + /* first, search the allow list */ + for(i = 0; !allow && i < access->nallow; i++) + { + rule = &access->allow[i]; + if(_access_check_match(&addr, &rule->ip, rule->mask)) + allow = 1; + } + + /* now the deny list */ + for(i = 0; !deny && i < access->ndeny; i++) + { + rule = &access->deny[i]; + if(_access_check_match(&addr, &rule->ip, rule->mask)) + deny = 1; + } + + /* allow then deny */ + if(access->order == 0) + { + if(allow) + return 1; + + if(deny) + return 0; + + /* allow by default */ + return 1; + } + + /* deny then allow */ + if(deny) + return 0; + + if(allow) + return 1; + + /* deny by default */ + return 0; +} diff --git a/util/base64.c b/util/base64.c new file mode 100644 index 00000000..b6d19a2b --- /dev/null +++ b/util/base64.c @@ -0,0 +1,236 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +/* base64 encoder/decoder. Originally part of main/util.c + * but moved here so that support/ab and ap_sha1.c could + * use it. This meant removing the ap_palloc()s and adding + * ugly 'len' functions, which is quite a nasty cost. + */ + +#include "util.h" + +/* aaaack but it's fast and const should make it shared text page. */ +static const unsigned char pr2six[256] = +{ + /* ASCII table */ + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 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, 64, 64, 64, 64, 64, + 64, 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, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +}; + +int ap_base64decode_len(const char *bufcoded, int buflen) +{ + int nbytesdecoded; + register const unsigned char *bufin; + register int nprbytes; + + bufin = (const unsigned char *) bufcoded; + while ((pr2six[*bufin] <= 63) && (buflen != 0)) { + bufin++; + buflen--; + } + + nprbytes = bufin - (const unsigned char *) bufcoded; + nbytesdecoded = (nprbytes * 3) / 4; + + return nbytesdecoded; +} + +int ap_base64decode(char *bufplain, const char *bufcoded, int buflen) +{ + int len; + + len = ap_base64decode_binary((unsigned char *) bufplain, bufcoded, buflen); + bufplain[len] = '\0'; + return len; +} + +int ap_base64decode_binary(unsigned char *bufplain, + const char *bufcoded, int buflen) +{ + int nbytesdecoded; + register const unsigned char *bufin; + register unsigned char *bufout; + register int nprbytes; + + bufin = (const unsigned char *) bufcoded; + while ((pr2six[*bufin] <= 63) && (buflen != 0)) { + bufin++; + buflen--; + } + nprbytes = bufin - (const unsigned char *) bufcoded; + nbytesdecoded = ((nprbytes + 3) / 4) * 3; + + bufout = (unsigned char *) bufplain; + bufin = (const unsigned char *) bufcoded; + + while (nprbytes > 4) { + *(bufout++) = + (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); + *(bufout++) = + (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); + *(bufout++) = + (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); + bufin += 4; + nprbytes -= 4; + } + + /* Note: (nprbytes == 1) would be an error, so just ingore that case */ + if (nprbytes > 1) { + *(bufout++) = + (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); + } + if (nprbytes > 2) { + *(bufout++) = + (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); + } + if (nprbytes > 3) { + *(bufout++) = + (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); + } + + nbytesdecoded -= (4 - nprbytes) & 3; + return nbytesdecoded; +} + +static const char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +int ap_base64encode_len(int len) +{ + return ((len + 2) / 3 * 4) + 1; +} + +int ap_base64encode(char *encoded, const char *string, int len) +{ + return ap_base64encode_binary(encoded, (const unsigned char *) string, len); +} + +int ap_base64encode_binary(char *encoded, + const unsigned char *string, int len) +{ + int i; + char *p; + + p = encoded; + for (i = 0; i < len - 2; i += 3) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int) (string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2) | + ((int) (string[i + 2] & 0xC0) >> 6)]; + *p++ = basis_64[string[i + 2] & 0x3F]; + } + if (i < len) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + if (i == (len - 1)) { + *p++ = basis_64[((string[i] & 0x3) << 4)]; + *p++ = '='; + } + else { + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int) (string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + } + + *p++ = '\0'; + return p - encoded; +} + +/* convenience functions for j2 */ +char *b64_encode(char *buf, int len) { + int elen; + char *out; + + if(len == 0) + len = strlen(buf); + + elen = ap_base64encode_len(len); + out = (char *) malloc(sizeof(char) * (elen + 1)); + + ap_base64encode(out, buf, len); + + return out; +} + +char *b64_decode(char *buf) { + int elen; + char *out; + + elen = ap_base64decode_len(buf, -1); + out = (char *) malloc(sizeof(char) * (elen + 1)); + + ap_base64decode(out, buf, -1); + + return out; +} diff --git a/util/base64.h b/util/base64.h new file mode 100644 index 00000000..84a86ad5 --- /dev/null +++ b/util/base64.h @@ -0,0 +1,43 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/base64.h + * @brief Base64 encoding + * @author Apache Software Foundation + * $Date: 2004/05/17 05:03:13 $ + * $Revision: 1.1 $ + */ + +#ifndef INCL_UTIL_BASE64_H +#define INCL_UTIL_BASE64_H 1 + +/* base64 functions */ +extern int ap_base64decode_len(const char *bufcoded); +extern int ap_base64decode(char *bufplain, const char *bufcoded); +extern int ap_base64decode_binary(unsigned char *bufplain, const char *bufcoded); +extern int ap_base64encode_len(int len); +extern int ap_base64encode(char *encoded, const char *string, int len); +extern int ap_base64encode_binary(char *encoded, const unsigned char *string, int len); + +/* convenience, result string must be free()'d by caller */ +extern char *b64_encode(char *buf, int len); +extern char *b64_decode(char *buf); + +#endif diff --git a/util/config.c b/util/config.c new file mode 100644 index 00000000..68358b1a --- /dev/null +++ b/util/config.c @@ -0,0 +1,297 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "util.h" +#include "expat/expat.h" + +/** new config structure */ +config_t config_new(void) +{ + config_t c; + + c = (config_t) malloc(sizeof(struct config_st)); + memset(c, 0, sizeof(struct config_st)); + + c->hash = xhash_new(501); + + c->nads = nad_cache_new(); + + return c; +} + +struct build_data +{ + nad_t nad; + int depth; +}; + +static void _config_startElement(void *arg, const char *name, const char **atts) +{ + struct build_data *bd = (struct build_data *) arg; + int i = 0; + + nad_append_elem(bd->nad, -1, (char *) name, bd->depth); + while(atts[i] != NULL) + { + nad_append_attr(bd->nad, -1, (char *) atts[i], (char *) atts[i + 1]); + i += 2; + } + + bd->depth++; +} + +static void _config_endElement(void *arg, const char *name) +{ + struct build_data *bd = (struct build_data *) arg; + + bd->depth--; +} + +static void _config_charData(void *arg, const char *str, int len) +{ + struct build_data *bd = (struct build_data *) arg; + + nad_append_cdata(bd->nad, (char *) str, len, bd->depth); +} + +/** turn an xml file into a config hash */ +int config_load(config_t c, char *file) +{ + struct build_data bd; + FILE *f; + XML_Parser p; + int done, len, end, i, j, attr; + char buf[1024], *next; + struct nad_elem_st **path; + config_elem_t elem; + + /* open the file */ + f = fopen(file, "r"); + if(f == NULL) + { + fprintf(stderr, "config_load: couldn't open %s for reading: %s\n", file, strerror(errno)); + return 1; + } + + /* new parser */ + p = XML_ParserCreate(NULL); + if(p == NULL) + { + fprintf(stderr, "config_load: couldn't allocate XML parser\n"); + fclose(f); + return 1; + } + + /* nice new nad to parse it into */ + bd.nad = nad_new(c->nads); + bd.depth = 0; + + /* setup the parser */ + XML_SetUserData(p, (void *) &bd); + XML_SetElementHandler(p, _config_startElement, _config_endElement); + XML_SetCharacterDataHandler(p, _config_charData); + + for(;;) + { + /* read that file */ + len = fread(buf, 1, 1024, f); + if(ferror(f)) + { + fprintf(stderr, "config_load: read error: %s\n", strerror(errno)); + XML_ParserFree(p); + fclose(f); + nad_free(bd.nad); + return 1; + } + done = feof(f); + + /* parse it */ + if(!XML_Parse(p, buf, len, done)) + { + fprintf(stderr, "config_load: parse error at line %d: %s\n", XML_GetCurrentLineNumber(p), XML_ErrorString(XML_GetErrorCode(p))); + XML_ParserFree(p); + fclose(f); + nad_free(bd.nad); + return 1; + } + + if(done) + break; + } + + /* done reading */ + XML_ParserFree(p); + fclose(f); + + /* now, turn the nad into a config hash */ + path = NULL; + len = 0, end = 0; + /* start at 1, so we skip the root element */ + for(i = 1; i < bd.nad->ecur; i++) + { + /* make sure we have enough room to add this element to our path */ + if(end <= bd.nad->elems[i].depth) + { + end = bd.nad->elems[i].depth + 1; + path = (struct nad_elem_st **) realloc((void *) path, sizeof(struct nad_elem_st *) * end); + } + + /* save this path element */ + path[bd.nad->elems[i].depth] = &bd.nad->elems[i]; + len = bd.nad->elems[i].depth + 1; + + /* construct the key from the current path */ + next = buf; + for(j = 1; j < len; j++) + { + strncpy(next, bd.nad->cdata + path[j]->iname, path[j]->lname); + next = next + path[j]->lname; + *next = '.'; + next++; + } + next--; + *next = '\0'; + + /* find the config element for this key */ + elem = xhash_get(c->hash, buf); + if(elem == NULL) + { + /* haven't seen it before, so create it */ + elem = pmalloco(xhash_pool(c->hash), sizeof(struct config_elem_st)); + xhash_put(c->hash, pstrdup(xhash_pool(c->hash), buf), elem); + } + + /* make room for this value .. can't easily realloc off a pool, so + * we do it this way and let _config_reaper clean up */ + elem->values = realloc((void *) elem->values, sizeof(char *) * (elem->nvalues + 1)); + + /* and copy it in */ + if(NAD_CDATA_L(bd.nad, i) > 0) + elem->values[elem->nvalues] = pstrdupx(xhash_pool(c->hash), NAD_CDATA(bd.nad, i), NAD_CDATA_L(bd.nad, i)); + else + elem->values[elem->nvalues] = "1"; + + /* make room for the attribute lists */ + elem->attrs = realloc((void *) elem->attrs, sizeof(char **) * (elem->nvalues + 1)); + elem->attrs[elem->nvalues] = NULL; + + /* count the attributes */ + for(attr = bd.nad->elems[i].attr, j = 0; attr >= 0; attr = bd.nad->attrs[attr].next, j++); + + /* make space */ + elem->attrs[elem->nvalues] = pmalloc(xhash_pool(c->hash), sizeof(char *) * (j * 2 + 2)); + + /* if we have some */ + if(j > 0) + { + /* copy them in */ + j = 0; + attr = bd.nad->elems[i].attr; + while(attr >= 0) + { + elem->attrs[elem->nvalues][j] = pstrdupx(xhash_pool(c->hash), NAD_ANAME(bd.nad, attr), NAD_ANAME_L(bd.nad, attr)); + elem->attrs[elem->nvalues][j + 1] = pstrdupx(xhash_pool(c->hash), NAD_AVAL(bd.nad, attr), NAD_AVAL_L(bd.nad, attr)); + + j += 2; + attr = bd.nad->attrs[attr].next; + } + } + + /* do this and we can use j_attr */ + elem->attrs[elem->nvalues][j] = NULL; + elem->attrs[elem->nvalues][j + 1] = NULL; + + elem->nvalues++; + } + + if(path != NULL) + free(path); + + if(c->nad != NULL) + nad_free(c->nad); + c->nad = bd.nad; + + return 0; +} + +/** get the config element for this key */ +config_elem_t config_get(config_t c, char *key) +{ + return xhash_get(c->hash, key); +} + +/** get config value n for this key */ +char *config_get_one(config_t c, char *key, int num) +{ + config_elem_t elem = xhash_get(c->hash, key); + + if(elem == NULL) + return NULL; + + if(num >= elem->nvalues) + return NULL; + + return elem->values[num]; +} + +/** how many values for this key? */ +int config_count(config_t c, char *key) +{ + config_elem_t elem = xhash_get(c->hash, key); + + if(elem == NULL) + return 0; + + return elem->nvalues; +} + +/** get an attr for this value */ +char *config_get_attr(config_t c, char *key, int num, char *attr) +{ + config_elem_t elem = xhash_get(c->hash, key); + + if(num >= elem->nvalues || elem->attrs == NULL || elem->attrs[num] == NULL) + return NULL; + + return j_attr((const char **) elem->attrs[num], attr); +} + +/** cleanup helper */ +static void _config_reaper(xht h, const char *key, void *val, void *arg) +{ + config_elem_t elem = (config_elem_t) val; + + free(elem->values); + free(elem->attrs); +} + +/** cleanup */ +void config_free(config_t c) +{ + xhash_walk(c->hash, _config_reaper, NULL); + + xhash_free(c->hash); + + nad_free(c->nad); + + nad_cache_free(c->nads); + + free(c); +} diff --git a/util/datetime.c b/util/datetime.c new file mode 100644 index 00000000..ed3377c8 --- /dev/null +++ b/util/datetime.c @@ -0,0 +1,140 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "util/util.h" + +/* ISO 8601 / JEP-0082 date/time manipulation */ + +/* formats */ +#define DT_DATETIME_P "%04d-%02d-%02dT%02d:%02d:%lf+%02d:%02d" +#define DT_DATETIME_M "%04d-%02d-%02dT%02d:%02d:%lf-%02d:%02d" +#define DT_DATETIME_Z "%04d-%02d-%02dT%02d:%02d:%lfZ" +#define DT_TIME_P "%02d:%02d:%lf+%02d:%02d" +#define DT_TIME_M "%02d:%02d:%lf-%02d:%02d" +#define DT_TIME_Z "%02d:%02d:%lfZ" +#define DT_LEGACY "%04d%02d%02dT%02d:%02d:%lf" + +time_t datetime_in(char *date) { + struct tm gmt, off; + double sec; + off_t fix = 0; + struct timeval tv; + struct timezone tz; + + assert((int) date); + + /* !!! sucks having to call this each time */ + tzset(); + + memset(&gmt, 0, sizeof(struct tm)); + memset(&off, 0, sizeof(struct tm)); + + if(sscanf(date, DT_DATETIME_P, + &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, + &gmt.tm_hour, &gmt.tm_min, &sec, + &off.tm_hour, &off.tm_min) == 8) { + gmt.tm_sec = (int) sec; + gmt.tm_year -= 1900; + gmt.tm_mon--; + fix = off.tm_hour * 3600 + off.tm_min * 60; + } + + else if(sscanf(date, DT_DATETIME_M, + &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, + &gmt.tm_hour, &gmt.tm_min, &sec, + &off.tm_hour, &off.tm_min) == 8) { + gmt.tm_sec = (int) sec; + gmt.tm_year -= 1900; + gmt.tm_mon--; + fix = - off.tm_hour * 3600 - off.tm_min * 60; + } + + else if(sscanf(date, DT_DATETIME_Z, + &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, + &gmt.tm_hour, &gmt.tm_min, &sec) == 6) { + gmt.tm_sec = (int) sec; + gmt.tm_year -= 1900; + gmt.tm_mon--; + fix = 0; + } + + else if(sscanf(date, DT_TIME_P, + &gmt.tm_hour, &gmt.tm_min, &sec, + &off.tm_hour, &off.tm_min) == 5) { + gmt.tm_sec = (int) sec; + fix = off.tm_hour * 3600 + off.tm_min * 60; + } + + else if(sscanf(date, DT_TIME_M, + &gmt.tm_hour, &gmt.tm_min, &sec, + &off.tm_hour, &off.tm_min) == 5) { + gmt.tm_sec = (int) sec; + fix = - off.tm_hour * 3600 - off.tm_min * 60; + } + + else if(sscanf(date, DT_TIME_Z, + &gmt.tm_hour, &gmt.tm_min, &sec) == 3) { + gmt.tm_sec = (int) sec; + fix = - off.tm_hour * 3600 - off.tm_min * 60; + } + + else if(sscanf(date, DT_LEGACY, + &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday, + &gmt.tm_hour, &gmt.tm_min, &sec) == 6) { + gmt.tm_sec = (int) sec; + gmt.tm_year -= 1900; + gmt.tm_mon--; + fix = 0; + } + + gmt.tm_isdst = -1; + + gettimeofday(&tv, &tz); + + return mktime(&gmt) + fix - (tz.tz_minuteswest * 60); +} + +void datetime_out(time_t t, datetime_t type, char *date, int datelen) { + struct tm *gmt; + + assert((int) type); + assert((int) date); + assert((int) datelen); + + gmt = gmtime(&t); + + switch(type) { + case dt_DATE: + snprintf(date, datelen, "%04d-%02d-%02d", gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday); + break; + + case dt_TIME: + snprintf(date, datelen, "%02d:%02d:%02dZ", gmt->tm_hour, gmt->tm_min, gmt->tm_sec); + break; + + case dt_DATETIME: + snprintf(date, datelen, "%04d-%02d-%02dT%02d:%02d:%02dZ", gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday, gmt->tm_hour, gmt->tm_min, gmt->tm_sec); + break; + + case dt_LEGACY: + snprintf(date, datelen, "%04d%02d%02dT%02d:%02d:%02d", gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday, gmt->tm_hour, gmt->tm_min, gmt->tm_sec); + break; + } +} diff --git a/util/datetime.h b/util/datetime.h new file mode 100644 index 00000000..d278513d --- /dev/null +++ b/util/datetime.h @@ -0,0 +1,47 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/datetime.h + * @brief ISO 8610 / JEP 82 date/time manipulation + * @author Robert Norris + * $Date: 2004/05/05 23:49:38 $ + * $Revision: 1.1 $ + */ + +#ifndef INCL_UTIL_DATETIME_H +#define INCL_UTIL_DATETIME_H 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +typedef enum { + dt_DATE = 1, + dt_TIME = 2, + dt_DATETIME = 3, + dt_LEGACY = 4 +} datetime_t; + +time_t datetime_in(char *date); +void datetime_out(time_t t, datetime_t type, char *date, int datelen); + +#endif diff --git a/util/hex.c b/util/hex.c new file mode 100644 index 00000000..338fc6bd --- /dev/null +++ b/util/hex.c @@ -0,0 +1,56 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* simple hex conversion functions */ + +/** turn raw into hex - out must be (inlen*2)+1 */ +void hex_from_raw(char *in, int inlen, char *out) { + int i, h, l; + + for(i = 0; i < inlen; i++) { + h = in[i] & 0xf0; + h >>= 4; + l = in[i] & 0x0f; + out[i * 2] = (h >= 0x0 && h <= 0x9) ? (h + 0x30) : (h + 0x57); + out[i * 2 + 1] = (l >= 0x0 && l <= 0x9) ? (l + 0x30) : (l + 0x57); + } + out[i * 2] = '\0'; +} + +/** turn hex into raw - out must be (inlen/2) */ +int hex_to_raw(char *in, int inlen, char *out) { + int i, o, h, l; + + /* need +ve even input */ + if(inlen == 0 || (inlen / 2 * 2) != inlen) + return 1; + + for(i = o = 0; i < inlen; i += 2, o++) { + h = (in[i] >= 0x30 && in[i] <= 0x39) ? (in[i] - 0x30) : (in[i] >= 0x41 && in[i] <= 0x64) ? (in[i] - 0x36) : (in[i] >= 0x61 && in[i] <= 0x66) ? (in[i] - 0x56) : -1; + l = (in[i + 1] >= 0x30 && in[i + 1] <= 0x39) ? (in[i + 1] - 0x30) : (in[i + 1] >= 0x41 && in[i + 1] <= 0x64) ? (in[i + 1] - 0x36) : (in[i + 1] >= 0x61 && in[i + 1] <= 0x66) ? (in[i + 1] - 0x56) : -1; + + if(h < 0 || l < 0) + return 1; + + out[o] = (h << 4) + l; + } + + return 0; +} diff --git a/util/inaddr.c b/util/inaddr.c new file mode 100644 index 00000000..4d89d571 --- /dev/null +++ b/util/inaddr.c @@ -0,0 +1,218 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** + * @file util/inaddr.c + * @brief object like wrapper around struct sockaddr_storage + * + * The functions in this file are used as a wrapper around struct + * sockaddr_storage to access this structure like an object. The structure + * is seen as an object that contains an IPv4 or an IPv6 address and + * these functions are the access methods to this object. + * + * @warning this is the same as mio/mio_inaddr.c - changes made here need to be + * made there too. is there anyway we can merge these without + * requiring mio to depend on util? + */ + +#include "util.h" + +/** + * set the address of a struct sockaddr_storage + * (modeled after the stdlib function inet_pton) + * + * @param src the address that should be assigned to the struct sockaddr_storage + * (either a dotted quad for IPv4 or a compressed IPv6 address) + * @param dst the struct sockaddr_storage that should get the new address + * @return 1 on success, 0 if address is not valid + */ +int j_inet_pton(char *src, struct sockaddr_storage *dst) +{ +#ifndef HAVE_INET_PTON + struct sockaddr_in *sin; + + memset(dst, 0, sizeof(struct sockaddr_storage)); + sin = (struct sockaddr_in *)dst; + + if(inet_aton(src, &sin->sin_addr)) + { + dst->ss_family = AF_INET; + return 1; + } + + return 0; +#else + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + memset(dst, 0, sizeof(struct sockaddr_storage)); + sin = (struct sockaddr_in *)dst; + sin6 = (struct sockaddr_in6 *)dst; + + if(inet_pton(AF_INET, src, &sin->sin_addr) > 0) + { + dst->ss_family = AF_INET; + return 1; + } + + if(inet_pton(AF_INET6, src, &sin6->sin6_addr) > 0) + { + dst->ss_family = AF_INET6; +#ifdef SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + return 1; + } + + return 0; +#endif +} + +/** + * get the string representation of an address in struct sockaddr_storage + * (modeled after the stdlib function inet_ntop) + * + * @param src the struct sockaddr_storage where the address should be read + * @param dst where to write the result + * @param size the size of the result buffer + * @return NULL if failed, pointer to the result otherwise + */ +const char *j_inet_ntop(struct sockaddr_storage *src, char *dst, size_t size) +{ +#ifndef HAVE_INET_NTOP + char *tmp; + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)src; + + /* if we don't have inet_ntop we only accept AF_INET + * it's unlikely that we would have use for AF_INET6 + */ + if(src->ss_family != AF_INET) + { + return NULL; + } + + tmp = inet_ntoa(sin->sin_addr); + + if(!tmp || strlen(tmp)>=size) + { + return NULL; + } + + strncpy(dst, tmp, size); + return dst; +#else + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + sin = (struct sockaddr_in *)src; + sin6 = (struct sockaddr_in6 *)src; + + switch(src->ss_family) + { + case AF_UNSPEC: + case AF_INET: + return inet_ntop(AF_INET, &sin->sin_addr, dst, size); + case AF_INET6: + return inet_ntop(AF_INET6, &sin6->sin6_addr, dst, size); + default: + return NULL; + } +#endif +} + +/** + * get the port number out of a struct sockaddr_storage + * + * @param sa the struct sockaddr_storage where we want to read the port + * @return the port number (already converted to host byte order!) + */ +int j_inet_getport(struct sockaddr_storage *sa) +{ + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + switch(sa->ss_family) + { + case AF_INET: + sin = (struct sockaddr_in *)sa; + return ntohs(sin->sin_port); + case AF_INET6: + sin6 = (struct sockaddr_in6 *)sa; + return ntohs(sin6->sin6_port); + default: + return 0; + } +} + +/** + * set the port number in a struct sockaddr_storage + * + * @param sa the struct sockaddr_storage where the port should be set + * @param port the port number that should be set (in host byte order) + * @return 1 on success, 0 if address family is not supported + */ +int j_inet_setport(struct sockaddr_storage *sa, in_port_t port) +{ + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + sin = (struct sockaddr_in *)sa; + sin6 = (struct sockaddr_in6 *)sa; + + switch(sa->ss_family) + { + case AF_INET: + sin->sin_port = htons(port); + return 1; + case AF_INET6: + sin6->sin6_port = htons(port); + return 1; + default: + return 0; + } +} + +/** + * calculate the size of an address structure + * (on some unices the stdlibc functions for socket handling want to get the + * size of the address structure that is contained in the + * struct sockaddr_storage, not the size of struct sockaddr_storage itself) + * + * @param sa the struct sockaddr_storage for which we want to get the size of the contained address structure + * @return the size of the contained address structure + */ +socklen_t j_inet_addrlen(struct sockaddr_storage *sa) +{ +#ifdef SIN6_LEN + if(sa->ss_len != 0) + return sa->ss_len; +#endif + switch(sa->ss_family) + { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + default: + return sizeof(struct sockaddr_storage); + } +} diff --git a/util/inaddr.h b/util/inaddr.h new file mode 100644 index 00000000..f0e2cae2 --- /dev/null +++ b/util/inaddr.h @@ -0,0 +1,70 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifndef INCL_INADDR_H +#define INCL_INADDR_H + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ac-stdint.h" + +#ifdef HAVE_STRING_H +#include +#endif + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#include "subst/subst.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * helpers for ip addresses + */ + +#include + +int j_inet_pton(char *src, struct sockaddr_storage *dst); +const char *j_inet_ntop(struct sockaddr_storage *src, char *dst, size_t size); +int j_inet_getport(struct sockaddr_storage *sa); +int j_inet_setport(struct sockaddr_storage *sa, in_port_t port); +socklen_t j_inet_addrlen(struct sockaddr_storage *sa); + +#ifdef __cplusplus +} +#endif + +#endif /* INCL_UTIL_H */ + + diff --git a/util/jid.c b/util/jid.c new file mode 100644 index 00000000..fc946565 --- /dev/null +++ b/util/jid.c @@ -0,0 +1,629 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "util.h" + +#ifdef HAVE_IDN +#include +#endif + +/** Forward declaration **/ +static jid_t jid_reset_components_internal(jid_t jid, const unsigned char *node, const unsigned char *domain, const unsigned char *resource, int prepare); + +/** preparation cache */ +prep_cache_t prep_cache_new(void) { +#ifdef HAVE_IDN + prep_cache_t pc; + + pc = (prep_cache_t) malloc(sizeof(struct prep_cache_st)); + memset(pc, 0, sizeof(struct prep_cache_st)); + + pc->node = xhash_new(301); + pc->domain = xhash_new(301); + pc->resource = xhash_new(301); + + return pc; +#else + return NULL; +#endif +} + +void prep_cache_free(prep_cache_t pc) { +#ifdef HAVE_IDN + xhash_free(pc->node); + xhash_free(pc->domain); + xhash_free(pc->resource); + free(pc); +#endif +} + +char *prep_cache_node_get(prep_cache_t pc, char *from) { + return (char *) xhash_get(pc->node, from); +} + +void prep_cache_node_set(prep_cache_t pc, char *from, char *to) { + xhash_put(pc->node, pstrdup(xhash_pool(pc->node), from), (void *) pstrdup(xhash_pool(pc->node), to)); +} + +char *prep_cache_domain_get(prep_cache_t pc, char *from) { + return (char *) xhash_get(pc->domain, from); +} + +void prep_cache_domain_set(prep_cache_t pc, char *from, char *to) { + xhash_put(pc->domain, pstrdup(xhash_pool(pc->domain), from), (void *) pstrdup(xhash_pool(pc->domain), to)); +} + +char *prep_cache_resource_get(prep_cache_t pc, char *from) { + return (char *) xhash_get(pc->resource, from); +} + +void prep_cache_resource_set(prep_cache_t pc, char *from, char *to) { + xhash_put(pc->resource, pstrdup(xhash_pool(pc->resource), from), (void *) pstrdup(xhash_pool(pc->resource), to)); +} + +/** do stringprep on the pieces */ +int jid_prep_pieces(prep_cache_t pc, char *node, char *domain, char *resource) { +#ifdef HAVE_IDN + char str[1024], *prep; + + /* no cache, so do a real prep */ + if(pc == NULL) { + if(node[0] != '\0') + if(stringprep_xmpp_nodeprep(node, 1024) != 0) + return 1; + + if(stringprep_nameprep(domain, 1024) != 0) + return 1; + + if(resource[0] != '\0') + if(stringprep_xmpp_resourceprep(node, 1024) != 0) + return 1; + + return 0; + } + + /* cache version */ + if(node[0] != '\0') { + strcpy(str, node); + prep = prep_cache_node_get(pc, str); + if(prep != NULL) + strcpy(node, prep); + else { + if(stringprep_xmpp_nodeprep(str, 1024) != 0) + return 1; + prep_cache_node_set(pc, node, str); + strcpy(node, str); + } + } + + strcpy(str, domain); + prep = prep_cache_domain_get(pc, str); + if(prep != NULL) + strcpy(domain, prep); + else { + if(stringprep_nameprep(str, 1024) != 0) + return 1; + prep_cache_domain_set(pc, domain, str); + strcpy(domain, str); + } + + if(resource[0] != '\0') { + strcpy(str, resource); + prep = prep_cache_resource_get(pc, str); + if(prep != NULL) + strcpy(resource, prep); + else { + if(stringprep_xmpp_resourceprep(str, 1024) != 0) + return 1; + prep_cache_resource_set(pc, resource, str); + strcpy(resource, str); + } + } + +#endif + return 0; +} + +/** do stringprep on the piece **/ +int jid_prep(jid_t jid) +{ + char node[MAXLEN_JID_COMP+1]; + char domain[MAXLEN_JID_COMP+1]; + char resource[MAXLEN_JID_COMP+1]; + + if(jid->node != NULL) { + strncpy(node, jid->node, MAXLEN_JID_COMP); + node[MAXLEN_JID_COMP]='\0'; + } + else + node[0] = '\0'; + + if(jid->domain != NULL) { + strncpy(domain, jid->domain, MAXLEN_JID_COMP); + domain[MAXLEN_JID_COMP]='\0'; + } + else + domain[0] = '\0'; + + if(jid->resource != NULL) { + strncpy(resource, jid->resource, MAXLEN_JID_COMP); + resource[MAXLEN_JID_COMP]='\0'; + } + else + resource[0] = '\0'; + + if(jid_prep_pieces(jid->pc, node, domain, resource) != 0) + return 1; + + /* put prepared components into jid */ + jid_reset_components_internal(jid, node, domain, resource, 0); + + return 0; +} + +/** make a new jid */ +jid_t jid_new(prep_cache_t pc, const unsigned char *id, int len) { + jid_t jid, ret; + + jid = malloc(sizeof(struct jid_st)); + jid->pc = pc; + jid->jid_data = NULL; + + ret = jid_reset(jid, id, len); + if(ret == NULL) { + if(len < 0) { + log_debug(ZONE, "invalid jid: %s", id); + } else { + log_debug(ZONE, "invalid jid: %.*s", len, id); + } + free(jid); + } + + return ret; +} + +/** Make jid to use static buffer (jid data won't be allocated dynamically, but + * given buffer will be always used. JID may not be previously used! */ +void jid_static(jid_t jid, jid_static_buf *buf) +{ + /* clear jid */ + memset(jid, 0, sizeof(*jid)); + + /* set buffer */ + jid->jid_data = (unsigned char *)buf; +} + + +/** build a jid from an id */ +jid_t jid_reset(jid_t jid, const unsigned char *id, int len) { + prep_cache_t pc; + unsigned char *myid, *cur, *olddata=NULL; + + assert((int) jid); + + pc = jid->pc; + if (jid->jid_data != NULL) { + if(jid->jid_data_len != 0) + free(jid->jid_data); + else + olddata = jid->jid_data; /* store pointer to old data */ + } + memset(jid, 0, sizeof(struct jid_st)); + jid->dirty = 1; + jid->pc = pc; + jid->node = ""; + jid->domain = ""; + jid->resource = ""; + + /* nice empty jid */ + if(id == NULL) + return jid; + + if(len < 0) + len = strlen(id); + + if((len == 0) || (len > MAXLEN_JID)) + return NULL; + + if(olddata != NULL) + myid = olddata; /* use static buffer */ + else { + jid->jid_data_len = sizeof(char) * (len + 1); + myid = (char *) malloc(jid->jid_data_len); + } + sprintf(myid, "%.*s", len, id); + + /* fail - only a resource or leading @ */ + if(myid[0] == '/' || myid[0] == '@') { + if(olddata == NULL) free(myid); + return NULL; + } + + /* get the resource first */ + cur = strstr(myid, "/"); + + if(cur != NULL) + { + *cur = '\0'; + cur++; + if(strlen(cur) > 0) { + jid->resource = cur; + } else { + /* fail - a resource separator but nothing after it */ + if(olddata == NULL) free(myid); + return NULL; + } + } + + /* find the domain */ + cur = strstr(myid, "@"); + if(cur != NULL) { + *cur = '\0'; + cur++; + if(strlen(cur) == 0) { + /* no domain part, bail out */ + if(olddata == NULL) free(myid); + return NULL; + } + jid->domain = cur; + jid->node = myid; + } else { + /* no @, so it's a domain only */ + jid->domain = myid; + } + + jid->jid_data = myid; + + if(jid_prep(jid) != 0) { + if(olddata == NULL) free(myid); + jid->jid_data = NULL; + return NULL; + } + + return jid; +} + +/** build a jid from components - internal version */ +static jid_t jid_reset_components_internal(jid_t jid, const unsigned char *node, const unsigned char *domain, const unsigned char *resource, int prepare) { + prep_cache_t pc; + unsigned char *olddata=NULL; + int node_l,domain_l,resource_l; + int dataStatic; + jid_static_buf staticTmpBuf; + + assert((int) jid); + + pc = jid->pc; + if(jid->jid_data != NULL) + olddata = jid->jid_data; /* Store old data before clearing JID */ + + dataStatic = ((jid->jid_data != NULL) && (jid->jid_data_len == 0)); + + free(jid->_user); + free(jid->_full); + + memset(jid, 0, sizeof(struct jid_st)); + jid->pc = pc; + + /* get lengths */ + node_l = strlen(node); + domain_l = strlen(domain); + resource_l = strlen(resource); + + if(node_l > MAXLEN_JID_COMP) + node_l = MAXLEN_JID_COMP; + + if(domain_l > MAXLEN_JID_COMP) + domain_l = MAXLEN_JID_COMP; + + if(resource_l > MAXLEN_JID_COMP) + resource_l = MAXLEN_JID_COMP; + + if(dataStatic) { + /* use static buffer */ + jid->jid_data = staticTmpBuf; + } + else { + /* allocate new data buffer */ + jid->jid_data_len = node_l+domain_l+resource_l+3; + jid->jid_data = malloc(jid->jid_data_len); + } + + /* copy to buffer */ + jid->node = jid->jid_data; + strncpy(jid->node, node, node_l); + jid->node[node_l] = 0; + + jid->domain = jid->node + node_l + 1; + strncpy(jid->domain, domain, domain_l); + jid->domain[domain_l] = 0; + + jid->resource = jid->domain + domain_l + 1; + strncpy(jid->resource, resource, resource_l); + jid->resource[resource_l] = 0; + + /* Free old data buffer. Postponed to this point so that arguments may point (in)to old jid data. */ + if((!dataStatic) && (olddata != NULL)) + free(olddata); + + if(prepare) { + if(jid_prep(jid) != 0) + return NULL; + } + + jid->dirty = 1; + + if (dataStatic) { + jid->jid_data = olddata; /* Return pointer to the original static buffer */ + memcpy(jid->jid_data,staticTmpBuf,node_l+domain_l+resource_l+3); /* Copy data from tmp buf to original buffer */ + + /* Relocate pointers */ + jid->node = olddata+(jid->node-(unsigned char *)staticTmpBuf); + jid->domain = olddata+(jid->domain-(unsigned char *)staticTmpBuf); + jid->resource = olddata+(jid->resource-(unsigned char *)staticTmpBuf); + } + + return jid; +} + +/** build a jid from components */ +jid_t jid_reset_components(jid_t jid, const unsigned char *node, const unsigned char *domain, const unsigned char *resource) { + return jid_reset_components_internal(jid, node, domain, resource, 1); +} + +/** free a jid */ +void jid_free(jid_t jid) +{ + if((jid->jid_data != NULL) && (jid->jid_data_len != 0)) + free(jid->jid_data); + free(jid->_user); + free(jid->_full); + free(jid); +} + +/** build user and full if they're out of date */ +void jid_expand(jid_t jid) +{ + int nlen, dlen, rlen, ulen; + + if((!jid->dirty) && (jid->_full)) + return; /* Not dirty & already expanded */ + + if(*jid->domain == '\0') { + /* empty */ + jid->_full = (unsigned char*) realloc(jid->_full, 1); + jid->_full[0] = 0; + return; + } + + nlen = strlen(jid->node); + dlen = strlen(jid->domain); + rlen = strlen(jid->resource); + + if(nlen == 0) { + ulen = dlen+1; + jid->_user = (unsigned char*) realloc(jid->_user, ulen); + strcpy(jid->_user, jid->domain); + } else { + ulen = nlen+1+dlen+1; + jid->_user = (unsigned char*) realloc(jid->_user, ulen); + snprintf(jid->_user, ulen, "%s@%s", jid->node, jid->domain); + } + + if(rlen == 0) { + jid->_full = (unsigned char*) realloc(jid->_full, ulen); + strcpy(jid->_full, jid->_user); + } else { + jid->_full = (unsigned char*) realloc(jid->_full, ulen+1+rlen); + snprintf(jid->_full, ulen+1+rlen, "%s/%s", jid->_user, jid->resource); + } + + jid->dirty = 0; +} + +/** expand and return the user */ +const unsigned char *jid_user(jid_t jid) +{ + jid_expand(jid); + + return jid->_user; +} + +/** expand and return the full */ +const unsigned char *jid_full(jid_t jid) +{ + jid_expand(jid); + + return jid->_full; +} + +/** compare the user portion of two jids */ +int jid_compare_user(jid_t a, jid_t b) +{ + jid_expand(a); + jid_expand(b); + + return strcmp(a->_user, b->_user); +} + +/** compare two full jids */ +int jid_compare_full(jid_t a, jid_t b) +{ + jid_expand(a); + jid_expand(b); + + return strcmp(a->_full, b->_full); +} + +/** duplicate a jid */ +jid_t jid_dup(jid_t jid) +{ + jid_t new; + + new = (jid_t) malloc(sizeof(struct jid_st)); + memcpy(new, jid, sizeof(struct jid_st)); + if(jid->jid_data != NULL) { + if(jid->jid_data_len == 0) { + /* when original jid had static buffer, allocate new dynamic buffer + * of the same size as has the static buffer */ + jid->jid_data_len = sizeof(jid_static_buf); + } + + /* allocate & populate new dynamic buffer */ + new->jid_data = malloc(new->jid_data_len); + memcpy(new->jid_data, jid->jid_data, new->jid_data_len); + + /* relocate pointers */ + if(jid->node[0] == '\0') + new->node = ""; + else + new->node = new->jid_data + (jid->node - jid->jid_data); + if(jid->domain[0] == '\0') + new->domain = ""; + else + new->domain = new->jid_data + (jid->domain - jid->jid_data); + if(jid->resource[0] == '\0') + new->resource = ""; + else + new->resource = new->jid_data + (jid->resource - jid->jid_data); + } + if(jid->_user) + new->_user = strdup(jid->_user); + if(jid->_full) + new->_full = strdup(jid->_full); + + return new; +} + +/** util to search through jids */ +int jid_search(jid_t list, jid_t jid) +{ + jid_t cur; + for(cur = list; cur != NULL; cur = cur->next) + if(jid_compare_full(cur,jid) == 0) + return 1; + return 0; +} + +/** remove a jid_t from a list, returning the new list */ +jid_t jid_zap(jid_t list, jid_t jid) +{ + jid_t cur, dead; + + if(jid == NULL || list == NULL) + return NULL; + + /* check first */ + if(jid_compare_full(jid,list) == 0) { + cur = list->next; + jid_free(list); + return cur; + } + + /* check through the list, stopping at the previous list entry to a matching one */ + cur = list; + while(cur != NULL) + { + if(cur->next == NULL) + /* none match, so we're done */ + return list; + + if(jid_compare_full(cur->next, jid) == 0) + { + /* match, kill it */ + dead = cur->next; + cur->next = cur->next->next; + jid_free(dead); + + return list; + } + + /* loop */ + cur = cur->next; + } + + /* shouldn't get here */ + return list; +} + +/** make a copy of jid, link into list (avoiding dups) */ +jid_t jid_append(jid_t list, jid_t jid) +{ + jid_t scan; + + if(list == NULL) + return jid_dup(jid); + + scan = list; + while(scan != NULL) + { + /* check for dups */ + if(jid_compare_full(scan, jid) == 0) + return list; + + /* tack it on to the end of the list */ + if(scan->next == NULL) + { + scan->next = jid_dup(jid); + return list; + } + + scan = scan->next; + } + + return list; +} + +/** create random resource **/ +void jid_random_part(jid_t jid, jid_part_t part) +{ + char hashBuf[41]; + char randomBuf[257]; + int i,r; + + /* create random string */ + for(i = 0; i < 256; i++) { + r = (int) (36.0 * rand() / RAND_MAX); + randomBuf[i] = (r >= 0 && r <= 0) ? (r + 48) : (r + 87); + } + randomBuf[256] = 0; + + /* hash it */ + shahash_r(randomBuf, hashBuf); + + /* change jid */ + switch(part) { + case jid_NODE: + jid_reset_components(jid, hashBuf, jid->domain, jid->resource); + break; + + case jid_DOMAIN: /* unused */ + jid_reset_components(jid, jid->node, hashBuf, jid->resource); + break; + + case jid_RESOURCE: + jid_reset_components(jid, jid->node, jid->domain, hashBuf); + break; + } + + /* prepare */ + jid_prep(jid); +} + diff --git a/util/jid.h b/util/jid.h new file mode 100644 index 00000000..115f48fd --- /dev/null +++ b/util/jid.h @@ -0,0 +1,109 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/jid.h + * @brief Jabber identifiers + * @author Robert Norris + * $Date: 2004/05/01 00:51:10 $ + * $Revision: 1.1 $ + * + * JID manipulation. Validity is checked via stringprep (if available), using + * the "nodeprep", "nameprep" and "resourceprep" profiles (see xmpp-core + * section 3). + * + * The application should fill out node, domain and resource directly, then + * call jid_expand(), or set the dirty flag. + */ + +#ifndef INCL_UTIL_JID_H +#define INCL_UTIL_JID_H 1 + +#include "pool.h" + +/* opaque decl */ +typedef struct _prep_cache_st *prep_cache_t; + +prep_cache_t prep_cache_new(pool_t p); + +/** these sizings come from xmpp-core */ +typedef struct _jid_st { + pool_t p; + + /* cache for prep, if any */ + prep_cache_t pc; + + /* basic components of the jid */ + unsigned char node[1024]; + unsigned char domain[1024]; + unsigned char resource[1024]; + + /* the "user" part of the jid (sans resource) */ + unsigned char *user; + int ulen; + + /* the complete jid */ + unsigned char *full; + int flen; + + /* application should set to 1 if user/full need regenerating */ + int dirty; + + /* for lists of jids */ + struct _jid_st *next; +} *jid_t; + +/** make a new jid, and call jid_reset() to populate it */ +jid_t jid_new(pool_t p, prep_cache_t pc, const unsigned char *id, int len); + +/** clear and populate the jid with the given id. if id == NULL, just clears the jid to 0 */ +jid_t jid_reset(jid_t jid, const unsigned char *id, int len); + +/** do string preparation on a jid */ +int jid_prep(jid_t jid); + +/** expands user and full if the dirty flag is set */ +void jid_expand(jid_t jid); + +/** return the user or full jid. these call jid_expand to make sure the user and + * full jid are up to date */ +const unsigned char *jid_user(jid_t jid); +const unsigned char *jid_full(jid_t jid); + +/** compare two user or full jids. these call jid_expand, then strcmp. returns + * 0 if they're the same, < 0 if a < b, > 0 if a > b */ +int jid_compare_user(jid_t a, jid_t b); +int jid_compare_full(jid_t a, jid_t b); + +/** duplicate a jid */ +jid_t jid_dup(jid_t jid, pool_t p); + +/** list helpers */ + +/** see if a jid is present in a list */ +int jid_search(jid_t list, jid_t jid); + +/** remove a jid from a list, and return the new list */ +jid_t jid_zap(jid_t list, jid_t jid); + +/** insert of a copy of jid into list, avoiding dups */ +jid_t jid_append(jid_t list, jid_t jid); + + +#endif diff --git a/util/jqueue.c b/util/jqueue.c new file mode 100644 index 00000000..0df3640c --- /dev/null +++ b/util/jqueue.c @@ -0,0 +1,127 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* priority jqueues */ + +#include "util.h" + +jqueue_t jqueue_new(void) { + pool p; + jqueue_t q; + + p = pool_new(); + q = (jqueue_t) pmalloco(p, sizeof(struct _jqueue_st)); + + q->p = p; + + return q; +} + +void jqueue_free(jqueue_t q) { + assert((int) q); + + pool_free(q->p); +} + +void jqueue_push(jqueue_t q, void *data, int priority) { + _jqueue_node_t qn, scan; + + assert((int) q); + + q->size++; + + /* node from the cache, or make a new one */ + qn = q->cache; + if(qn != NULL) + q->cache = qn->next; + else + qn = (_jqueue_node_t) pmalloc(q->p, sizeof(struct _jqueue_node_st)); + + qn->data = data; + qn->priority = priority; + + qn->next = NULL; + qn->prev = NULL; + + /* first one */ + if(q->back == NULL && q->front == NULL) { + q->back = qn; + q->front = qn; + + return; + } + + /* find the first node with priority <= to us */ + for(scan = q->back; scan != NULL && scan->priority > priority; scan = scan->next); + + /* didn't find one, so we have top priority - push us on the front */ + if(scan == NULL) { + qn->prev = q->front; + qn->prev->next = qn; + q->front = qn; + + return; + } + + /* push us in front of scan */ + qn->next = scan; + qn->prev = scan->prev; + + if(scan->prev != NULL) + scan->prev->next = qn; + else + q->back = qn; + + scan->prev = qn; +} + +void *jqueue_pull(jqueue_t q) { + void *data; + _jqueue_node_t qn; + + assert((int) q); + + if(q->front == NULL) + return NULL; + + data = q->front->data; + + qn = q->front; + + if(qn->prev != NULL) + qn->prev->next = NULL; + + q->front = qn->prev; + + /* node to cache for later reuse */ + qn->next = q->cache; + q->cache = qn; + + if(q->front == NULL) + q->back = NULL; + + q->size--; + + return data; +} + +int jqueue_size(jqueue_t q) { + return q->size; +} diff --git a/util/jsignal.c b/util/jsignal.c new file mode 100644 index 00000000..059ccb5c --- /dev/null +++ b/util/jsignal.c @@ -0,0 +1,23 @@ +/* + * A compatible implementation of signal which relies of sigaction. + * More or less taken from teh Stevens book. + */ + +#include +#include + +jsighandler_t* jabber_signal(int signo, jsighandler_t *func) +{ + struct sigaction act, oact; + + act.sa_handler = func; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; +#ifdef SA_RESTART + if (signo != SIGALRM) + act.sa_flags |= SA_RESTART; +#endif + if (sigaction(signo, &act, &oact) < 0) + return (SIG_ERR); + return (oact.sa_handler); +} diff --git a/util/log.c b/util/log.c new file mode 100644 index 00000000..57b13aef --- /dev/null +++ b/util/log.c @@ -0,0 +1,210 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "util.h" + +#define MAX_LOG_LINE (1024) + +#ifdef DEBUG +static int debug_flag; +#endif + +static const char *_log_level[] = +{ + "emergency", + "alert", + "critical", + "error", + "warning", + "notice", + "info", + "debug" +}; + +static log_facility_t _log_facilities[] = { + { "local0", LOG_LOCAL0 }, + { "local1", LOG_LOCAL1 }, + { "local2", LOG_LOCAL2 }, + { "local3", LOG_LOCAL3 }, + { "local4", LOG_LOCAL4 }, + { "local5", LOG_LOCAL5 }, + { "local6", LOG_LOCAL6 }, + { "local7", LOG_LOCAL7 }, + { NULL, -1 } +}; + +static int _log_facility(char *facility) { + log_facility_t *lp; + + if (facility == NULL) { + return -1; + } + for (lp = _log_facilities; lp->facility; lp++) { + if (!strcasecmp(lp->facility, facility)) { + break; + } + } + return lp->number; +} + +log_t log_new(log_type_t type, char *ident, char *facility) +{ + log_t log; + int fnum = 0; + + log = (log_t) malloc(sizeof(struct log_st)); + memset(log, 0, sizeof(struct log_st)); + + log->type = type; + + if(type == log_SYSLOG) { + fnum = _log_facility(facility); + if (fnum < 0) + fnum = LOG_LOCAL7; + openlog(ident, LOG_PID, fnum); + return log; + } + + else if(type == log_STDOUT) { + log->file = stdout; + return log; + } + + log->file = fopen(ident, "a+"); + if(log->file == NULL) + { + fprintf(stderr, + "ERROR: couldn't open logfile: %s\n" + " logging will go to stdout instead\n", strerror(errno)); + log->type = log_STDOUT; + log->file = stdout; + } + + return log; +} + +void log_write(log_t log, int level, const char *msgfmt, ...) +{ + va_list ap; + char *pos, message[MAX_LOG_LINE]; + int sz; + time_t t; + + if(log->type == log_SYSLOG) { + va_start(ap, msgfmt); +#ifdef HAVE_VSYSLOG + vsyslog(level, msgfmt, ap); +#else + vsnprintf(message, MAX_LOG_LINE, msgfmt, ap); + syslog(level, "%s", message); +#endif + va_end(ap); + +#ifndef DEBUG + return; +#endif + } + + /* timestamp */ + t = time(NULL); + pos = ctime(&t); + sz = strlen(pos); + /* chop off the \n */ + pos[sz-1]=' '; + + /* insert the header */ + snprintf(message, MAX_LOG_LINE, "%s[%s] ", pos, _log_level[level]); + + /* find the end and attach the rest of the msg */ + for (pos = message; *pos != '\0'; pos++); /*empty statement */ + sz = pos - message; + va_start(ap, msgfmt); + vsnprintf(pos, MAX_LOG_LINE - sz, msgfmt, ap); + va_end(ap); +#ifdef DEBUG + if(log->type != log_SYSLOG) { +#endif + fprintf(log->file,"%s", message); + fprintf(log->file, "\n"); + fflush(log->file); +#ifdef DEBUG + } +#endif + +#ifdef DEBUG + /* If we are in debug mode we want everything copied to the stdout */ + if (get_debug_flag() && log->type != log_STDOUT) { + fprintf(stdout, "%s\n", message); + fflush(stdout); + } +#endif /*DEBUG*/ +} + +void log_free(log_t log) { + if(log->type == log_SYSLOG) + closelog(); + else if(log->type == log_FILE) + fclose(log->file); + + free(log); +} + +#ifdef DEBUG +/** debug logging */ +void debug_log(char *file, int line, const char *msgfmt, ...) +{ + va_list ap; + char *pos, message[MAX_DEBUG]; + int sz; + time_t t; + + /* timestamp */ + t = time(NULL); + pos = ctime(&t); + sz = strlen(pos); + /* chop off the \n */ + pos[sz-1]=' '; + + /* insert the header */ + snprintf(message, MAX_DEBUG, "%s%s:%d ", pos, file, line); + + /* find the end and attach the rest of the msg */ + for (pos = message; *pos != '\0'; pos++); /*empty statement */ + sz = pos - message; + va_start(ap, msgfmt); + vsnprintf(pos, MAX_DEBUG - sz, msgfmt, ap); + fprintf(stderr,"%s", message); + fprintf(stderr, "\n"); + fflush(stderr); +} + +int get_debug_flag(void) +{ + return debug_flag; +} + +void set_debug_flag(int v) +{ + debug_flag = v; +} +#else /* DEBUG */ +void debug_log(char *file, int line, const char *msgfmt, ...) +{ } +#endif diff --git a/util/log.h b/util/log.h new file mode 100644 index 00000000..fafd2db8 --- /dev/null +++ b/util/log.h @@ -0,0 +1,69 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/log.h + * @brief logging functions + * @author Robert Norris + * $Revision: 1.1 $ + * $Date: 2004/04/30 00:53:54 $ + */ + +#ifndef INCL_UTIL_LOG_H +#define INCL_UTIL_LOG_H 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_SYSLOG_H +# include +#endif + +#include "pool.h" + +typedef enum { + log_STDOUT, + log_SYSLOG, + log_FILE +} log_type_t; + +/* opaque decl */ +typedef struct _log_st *log_t; + +extern log_t log_new(pool_t p, log_type_t type, char *ident, char *facility); +extern void log_write(log_t log, int level, const char *msgfmt, ...); + +/* debug logging */ +#ifdef DEBUG +extern int log_debug_flag; +void __log_debug(char *file, int line, char *subsys, const char *msgfmt, ...); + +# define log_debug_get_flag() log_debug_flag +# define log_debug_set_flag(f) (log_debug_flag = f ? 1 : 0) +# define log_debug(...) if(log_debug_flag) __log_debug(__FILE__,__LINE__,0,__VA_ARGS__) +# define log_debug_subsys(...) if(log_debug_flag) __log_debug(__FILE__,__LINE__,__VA_ARGS__) +#else +# define log_debug_get_flag() (0) +# define log_debug_set_flag(f) +# define log_debug(...) +# define log_debug_subsys(...) +#endif + +#endif diff --git a/util/md5.c b/util/md5.c new file mode 100644 index 00000000..6d9d3ca9 --- /dev/null +++ b/util/md5.c @@ -0,0 +1,381 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.c,v 1.5 2005/06/02 04:48:25 zion Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5.h" +#include + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} diff --git a/util/md5.h b/util/md5.h new file mode 100644 index 00000000..cb9f56ca --- /dev/null +++ b/util/md5.h @@ -0,0 +1,93 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.6 2005/06/02 04:48:25 zion Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +#include "util.h" + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef uint8_t md5_byte_t; /* 8-bit byte */ +typedef uint32_t md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/util/misc.c b/util/misc.c new file mode 100644 index 00000000..4efdfb42 --- /dev/null +++ b/util/misc.c @@ -0,0 +1,43 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "misc.h" + +#include +#include + +#define BLOCKSIZE (1024) + +int misc_realloc(void **blocks, int len) { + void *nblocks; + int nlen; + + /* round up to standard block sizes */ + nlen = (((len - 1) / BLOCKSIZE) + 1) * BLOCKSIZE; + + /* keep trying till we get it */ + if((nblocks = realloc(*blocks, nlen)) == NULL) { + fprintf(stderr, "fatal: out of memory\n"); + abort(); + } + + *blocks = nblocks; + return nlen; +} diff --git a/util/misc.h b/util/misc.h new file mode 100644 index 00000000..d550728b --- /dev/null +++ b/util/misc.h @@ -0,0 +1,34 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/misc.h + * @brief Miscellaneous utilities + * @author Robert Norris + * $Revision: 1.1 $ + * $Date: 2004/05/01 00:51:10 $ + */ + +#ifndef INCL_UTIL_MISC_H +#define INCL_UTIL_MISC_H 1 + +int misc_realloc(void **blocks, int len); +#define misc_alloc(blocks, size, len) if((size) > len) len = misc_realloc((void **) &(blocks), (size)) + +#endif diff --git a/util/nad.c b/util/nad.c new file mode 100644 index 00000000..fb42327a --- /dev/null +++ b/util/nad.c @@ -0,0 +1,1145 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** + * !!! Things to do (after 2.0) + * + * - make nad_find_scoped_namespace() take an element index, and only search + * the scope on that element (currently, it searchs all elements from + * end to start, which isn't really correct, though it works in most cases + * + * - new functions: + * * insert one nad (or part thereof) into another nad + * * clear a part of a nad (like xmlnode_hide) + * + * - audit use of depth array and parent (see j2 bug #792) + */ + +#include "util.h" + +#ifdef HAVE_EXPAT +#include "expat/expat.h" +#endif + +/* define NAD_DEBUG to get pointer tracking - great for weird bugs that you can't reproduce */ +#ifdef NAD_DEBUG + +static xht _nad_alloc_tracked = NULL; +static xht _nad_free_tracked = NULL; + +static void _nad_ptr_check(const char *func, nad_t nad) { + char loc[24]; + snprintf(loc, sizeof(loc), "%x", (int) nad); + + if(xhash_get(_nad_alloc_tracked, loc) == NULL) { + fprintf(stderr, ">>> NAD OP %s: 0x%x not allocated!\n", func, (int) nad); + abort(); + } + + if(xhash_get(_nad_free_tracked, loc) != NULL) { + fprintf(stderr, ">>> NAD OP %s: 0x%x previously freed!\n", func, (int) nad); + abort(); + } + + fprintf(stderr, ">>> NAD OP %s: 0x%x\n", func, (int) nad); +} +#else +#define _nad_ptr_check(func,nad) +#endif + +#define BLOCKSIZE 1024 + +/** internal: do and return the math and ensure it gets realloc'd */ +int _nad_realloc(void **oblocks, int len) +{ + void *nblocks; + int nlen; + + /* round up to standard block sizes */ + nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE; + + /* keep trying till we get it */ + while((nblocks = realloc(*oblocks, nlen)) == NULL) sleep(1); + *oblocks = nblocks; + return nlen; +} + +/** this is the safety check used to make sure there's always enough mem */ +#define NAD_SAFE(blocks, size, len) if((size) > len) len = _nad_realloc((void**)&(blocks),(size)); + +/** internal: append some cdata and return the index to it */ +int _nad_cdata(nad_t nad, const char *cdata, int len) +{ + NAD_SAFE(nad->cdata, nad->ccur + len, nad->clen); + + memcpy(nad->cdata + nad->ccur, cdata, len); + nad->ccur += len; + return nad->ccur - len; +} + +/** internal: create a new attr on any given elem */ +int _nad_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen) +{ + int attr; + + /* make sure there's mem for us */ + NAD_SAFE(nad->attrs, (nad->acur + 1) * sizeof(struct nad_attr_st), nad->alen); + + attr = nad->acur; + nad->acur++; + nad->attrs[attr].next = nad->elems[elem].attr; + nad->elems[elem].attr = attr; + nad->attrs[attr].lname = strlen(name); + nad->attrs[attr].iname = _nad_cdata(nad,name,nad->attrs[attr].lname); + if(vallen > 0) + nad->attrs[attr].lval = vallen; + else + nad->attrs[attr].lval = strlen(val); + nad->attrs[attr].ival = _nad_cdata(nad,val,nad->attrs[attr].lval); + nad->attrs[attr].my_ns = ns; + + return attr; +} + +/** create a new cache, simple pointer to a list of nads */ +nad_cache_t nad_cache_new(void) +{ + nad_cache_t cache; + while((cache = malloc(sizeof(nad_cache_t))) == NULL) sleep(1); + *cache = NULL; + +#ifdef NAD_DEBUG + if(_nad_alloc_tracked == NULL) _nad_alloc_tracked = xhash_new(501); + if(_nad_free_tracked == NULL) _nad_free_tracked = xhash_new(501); +#endif + + return cache; +} + + +/** free the cache and any nads in it */ +void nad_cache_free(nad_cache_t cache) +{ + nad_t cur; + while((cur = *cache) != NULL) + { + *cache = cur->next; + free(cur->elems); + free(cur->attrs); + free(cur->nss); + free(cur->cdata); + free(cur->depths); + free(cur); + } + free(cache); +} + +/** get the next nad from the cache, or create some */ +nad_t nad_new(nad_cache_t cache) +{ + nad_t nad; + +#ifndef NAD_DEBUG + /* If cache==NULL, then this NAD is not in a cache */ + + if ((cache!=NULL) && (*cache != NULL)) + { + nad = *cache; + *cache = nad->next; + nad->ccur = nad->ecur = nad->acur = nad->ncur = 0; + nad->scope = -1; + nad->cache = cache; + nad->next = NULL; + return nad; + } +#endif + + while((nad = malloc(sizeof(struct nad_st))) == NULL) sleep(1); + memset(nad,0,sizeof(struct nad_st)); + + nad->scope = -1; + nad->cache = cache; + +#ifdef NAD_DEBUG + { + char loc[24]; + snprintf(loc, sizeof(loc), "%x", (int) nad); + xhash_put(_nad_alloc_tracked, pstrdup(xhash_pool(_nad_alloc_tracked), loc), (void *) 1); + } + _nad_ptr_check(__func__, nad); +#endif + + return nad; +} + +nad_t nad_copy(nad_t nad) +{ + nad_t copy; + + _nad_ptr_check(__func__, nad); + + if(nad == NULL) return NULL; + + /* create a new nad not participating in a cache */ + copy = nad_new(NULL); + + /* if it's not large enough, make bigger */ + NAD_SAFE(copy->elems, nad->elen, copy->elen); + NAD_SAFE(copy->attrs, nad->alen, copy->alen); + NAD_SAFE(copy->nss, nad->nlen, copy->nlen); + NAD_SAFE(copy->cdata, nad->clen, copy->clen); + + /* copy all data */ + memcpy(copy->elems, nad->elems, nad->elen); + memcpy(copy->attrs, nad->attrs, nad->alen); + memcpy(copy->nss, nad->nss, nad->nlen); + memcpy(copy->cdata, nad->cdata, nad->clen); + + /* sync data */ + copy->ecur = nad->ecur; + copy->acur = nad->acur; + copy->ncur = nad->ncur; + copy->ccur = nad->ccur; + + copy->scope = nad->scope; + + return copy; +} + +/** free nad, or plug nad back in the cache */ +void nad_free(nad_t nad) +{ + if(nad == NULL) return; + +#ifdef NAD_DEBUG + _nad_ptr_check(__func__, nad); + { + char loc[24]; + snprintf(loc, sizeof(loc), "%x", (int) nad); + xhash_zap(_nad_alloc_tracked, loc); + xhash_put(_nad_free_tracked, pstrdup(xhash_pool(_nad_free_tracked), loc), (void *) nad); + } +#else + /* If nad->cache != NULL, then put back into cache, otherwise this nad is not in a cache */ + + if (nad->cache != NULL) { + nad->next = *(nad->cache); + *(nad->cache) = nad; + return; + } +#endif + + /* Free nad */ + free(nad->elems); + free(nad->attrs); + free(nad->cdata); + free(nad->nss); + free(nad->depths); +#ifndef NAD_DEBUG + free(nad); +#endif +} + +/** locate the next elem at a given depth with an optional matching name */ +int nad_find_elem(nad_t nad, int elem, int ns, const char *name, int depth) +{ + int my_ns; + int lname = 0; + + _nad_ptr_check(__func__, nad); + + /* make sure there are valid args */ + if(elem >= nad->ecur || name == NULL) return -1; + + /* set up args for searching */ + depth = nad->elems[elem].depth + depth; + if(name != NULL) lname = strlen(name); + + /* search */ + for(elem++;elem < nad->ecur;elem++) + { + /* if we hit one with a depth less than ours, then we don't have the + * same parent anymore, bail */ + if(nad->elems[elem].depth < depth) + return -1; + + if(nad->elems[elem].depth == depth && (lname <= 0 || (lname == nad->elems[elem].lname && strncmp(name,nad->cdata + nad->elems[elem].iname, lname) == 0)) && + (ns < 0 || ((my_ns = nad->elems[elem].my_ns) >= 0 && NAD_NURI_L(nad, ns) == NAD_NURI_L(nad, my_ns) && strncmp(NAD_NURI(nad, ns), NAD_NURI(nad, my_ns), NAD_NURI_L(nad, ns)) == 0))) + return elem; + } + + return -1; +} + +/** get a matching attr on this elem, both name and optional val */ +int nad_find_attr(nad_t nad, int elem, int ns, const char *name, const char *val) +{ + int attr, my_ns; + int lname, lval = 0; + + _nad_ptr_check(__func__, nad); + + /* make sure there are valid args */ + if(elem >= nad->ecur || name == NULL) return -1; + + attr = nad->elems[elem].attr; + lname = strlen(name); + if(val != NULL) lval = strlen(val); + + while(attr >= 0) + { + /* hefty, match name and if a val, also match that */ + if(lname == nad->attrs[attr].lname && strncmp(name,nad->cdata + nad->attrs[attr].iname, lname) == 0 && + (lval <= 0 || (lval == nad->attrs[attr].lval && strncmp(val,nad->cdata + nad->attrs[attr].ival, lval) == 0)) && + (ns < 0 || ((my_ns = nad->attrs[attr].my_ns) >= 0 && NAD_NURI_L(nad, ns) == NAD_NURI_L(nad, my_ns) && strncmp(NAD_NURI(nad, ns), NAD_NURI(nad, my_ns), NAD_NURI_L(nad, ns)) == 0))) + return attr; + attr = nad->attrs[attr].next; + } + return -1; +} + +/** get a matching ns on this elem, both uri and optional prefix */ +int nad_find_namespace(nad_t nad, int elem, const char *uri, const char *prefix) +{ + int check, ns; + + _nad_ptr_check(__func__, nad); + + if(uri == NULL) + return -1; + + /* work backwards through our parents, looking for our namespace on each one. + * if we find it, link it. if not, the namespace is undeclared - for now, just drop it */ + check = elem; + while(check >= 0) + { + ns = nad->elems[check].ns; + while(ns >= 0) + { + if(strlen(uri) == NAD_NURI_L(nad, ns) && strncmp(uri, NAD_NURI(nad, ns), NAD_NURI_L(nad, ns)) == 0 && (prefix == NULL || (nad->nss[ns].iprefix >= 0 && strlen(prefix) == NAD_NPREFIX_L(nad, ns) && strncmp(prefix, NAD_NPREFIX(nad, ns), NAD_NPREFIX_L(nad, ns)) == 0))) + return ns; + ns = nad->nss[ns].next; + } + check = nad->elems[check].parent; + } + + return -1; +} + +/** find a namespace in scope */ +int nad_find_scoped_namespace(nad_t nad, const char *uri, const char *prefix) +{ + int ns; + + _nad_ptr_check(__func__, nad); + + if(uri == NULL) + return -1; + + for(ns = 0; ns < nad->ncur; ns++) + { + if(strlen(uri) == NAD_NURI_L(nad, ns) && strncmp(uri, NAD_NURI(nad, ns), NAD_NURI_L(nad, ns)) == 0 && + (prefix == NULL || + (nad->nss[ns].iprefix >= 0 && + strlen(prefix) == NAD_NPREFIX_L(nad, ns) && strncmp(prefix, NAD_NPREFIX(nad, ns), NAD_NPREFIX_L(nad, ns)) == 0))) + return ns; + } + + return -1; +} + +/** create, update, or zap any matching attr on this elem */ +void nad_set_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen) +{ + int attr; + + _nad_ptr_check(__func__, nad); + + /* find one to replace first */ + if((attr = nad_find_attr(nad, elem, ns, name, NULL)) < 0) + { + /* only create new if there's a value to store */ + if(val != NULL) + _nad_attr(nad, elem, ns, name, val, vallen); + return; + } + + /* got matching, update value or zap */ + if(val == NULL) + { + nad->attrs[attr].lval = nad->attrs[attr].lname = 0; + }else{ + if(vallen > 0) + nad->attrs[attr].lval = vallen; + else + nad->attrs[attr].lval = strlen(val); + nad->attrs[attr].ival = _nad_cdata(nad,val,nad->attrs[attr].lval); + } + +} + +/** shove in a new child elem after the given one */ +int nad_insert_elem(nad_t nad, int parent, int ns, const char *name, const char *cdata) +{ + int elem = parent + 1; + + _nad_ptr_check(__func__, nad); + + NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen); + + /* relocate all the rest of the elems (unless we're at the end already) */ + if(nad->ecur != elem) + memmove(&nad->elems[elem + 1], &nad->elems[elem], (nad->ecur - elem) * sizeof(struct nad_elem_st)); + nad->ecur++; + + /* set up req'd parts of new elem */ + nad->elems[elem].parent = parent; + nad->elems[elem].lname = strlen(name); + nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname); + nad->elems[elem].attr = -1; + nad->elems[elem].ns = nad->scope; nad->scope = -1; + nad->elems[elem].itail = nad->elems[elem].ltail = 0; + nad->elems[elem].my_ns = ns; + + /* add cdata if given */ + if(cdata != NULL) + { + nad->elems[elem].lcdata = strlen(cdata); + nad->elems[elem].icdata = _nad_cdata(nad,cdata,nad->elems[elem].lcdata); + }else{ + nad->elems[elem].icdata = nad->elems[elem].lcdata = 0; + } + + /* parent/child */ + nad->elems[elem].depth = nad->elems[parent].depth + 1; + + return elem; +} + +/** wrap an element with another element */ +void nad_wrap_elem(nad_t nad, int elem, int ns, const char *name) +{ + int cur; + + _nad_ptr_check(__func__, nad); + + if(elem >= nad->ecur) return; + + NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen); + + /* relocate all the rest of the elems after us */ + memmove(&nad->elems[elem + 1], &nad->elems[elem], (nad->ecur - elem) * sizeof(struct nad_elem_st)); + nad->ecur++; + + /* set up req'd parts of new elem */ + nad->elems[elem].lname = strlen(name); + nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname); + nad->elems[elem].attr = -1; + nad->elems[elem].ns = nad->scope; nad->scope = -1; + nad->elems[elem].itail = nad->elems[elem].ltail = 0; + nad->elems[elem].icdata = nad->elems[elem].lcdata = 0; + nad->elems[elem].my_ns = ns; + + /* raise the bar on all the children */ + nad->elems[elem+1].depth++; + for(cur = elem + 2; cur < nad->ecur && nad->elems[cur].depth > nad->elems[elem].depth; cur++) nad->elems[cur].depth++; + + /* relink the parents */ + nad->elems[elem].parent = nad->elems[elem + 1].parent; + nad->elems[elem + 1].parent = elem; +} + +/** create a new elem on the list */ +int nad_append_elem(nad_t nad, int ns, const char *name, int depth) +{ + int elem; + + _nad_ptr_check(__func__, nad); + + /* make sure there's mem for us */ + NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen); + + elem = nad->ecur; + nad->ecur++; + nad->elems[elem].lname = strlen(name); + nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname); + nad->elems[elem].icdata = nad->elems[elem].lcdata = 0; + nad->elems[elem].itail = nad->elems[elem].ltail = 0; + nad->elems[elem].attr = -1; + nad->elems[elem].ns = nad->scope; nad->scope = -1; + nad->elems[elem].depth = depth; + nad->elems[elem].my_ns = ns; + + /* make sure there's mem in the depth array, then track us */ + NAD_SAFE(nad->depths, (depth + 1) * sizeof(int), nad->dlen); + nad->depths[depth] = elem; + + /* our parent is the previous guy in the depth array */ + if(depth <= 0) + nad->elems[elem].parent = -1; + else + nad->elems[elem].parent = nad->depths[depth - 1]; + + return elem; +} + +/** attach new attr to the last elem */ +int nad_append_attr(nad_t nad, int ns, const char *name, const char *val) +{ + _nad_ptr_check(__func__, nad); + + return _nad_attr(nad, nad->ecur - 1, ns, name, val, 0); +} + +/** append new cdata to the last elem */ +void nad_append_cdata(nad_t nad, const char *cdata, int len, int depth) +{ + int elem = nad->ecur - 1; + + _nad_ptr_check(__func__, nad); + + /* make sure this cdata is the child of the last elem to append */ + if(nad->elems[elem].depth == depth - 1) + { + if(nad->elems[elem].icdata == 0) + nad->elems[elem].icdata = nad->ccur; + _nad_cdata(nad,cdata,len); + nad->elems[elem].lcdata += len; + return; + } + + /* otherwise, pin the cdata on the tail of the last element at this depth */ + elem = nad->depths[depth]; + if(nad->elems[elem].itail == 0) + nad->elems[elem].itail = nad->ccur; + _nad_cdata(nad,cdata,len); + nad->elems[elem].ltail += len; +} + +/** bring a new namespace into scope */ +int nad_add_namespace(nad_t nad, const char *uri, const char *prefix) +{ + int ns; + + _nad_ptr_check(__func__, nad); + + /* only add it if its not already in scope */ + ns = nad_find_scoped_namespace(nad, uri, NULL); + if(ns >= 0) + return ns; + + /* make sure there's mem for us */ + NAD_SAFE(nad->nss, (nad->ncur + 1) * sizeof(struct nad_ns_st), nad->nlen); + + ns = nad->ncur; + nad->ncur++; + nad->nss[ns].next = nad->scope; + nad->scope = ns; + + nad->nss[ns].luri = strlen(uri); + nad->nss[ns].iuri = _nad_cdata(nad, uri, nad->nss[ns].luri); + if(prefix != NULL) + { + nad->nss[ns].lprefix = strlen(prefix); + nad->nss[ns].iprefix = _nad_cdata(nad, prefix, nad->nss[ns].lprefix); + } + else + nad->nss[ns].iprefix = -1; + + return ns; +} + +/** declare a namespace on an already-existing element */ +int nad_append_namespace(nad_t nad, int elem, const char *uri, const char *prefix) { + int ns; + + _nad_ptr_check(__func__, nad); + + /* see if its already scoped on this element */ + ns = nad_find_namespace(nad, elem, uri, NULL); + if(ns >= 0) + return ns; + + /* make some room */ + NAD_SAFE(nad->nss, (nad->ncur + 1) * sizeof(struct nad_ns_st), nad->nlen); + + ns = nad->ncur; + nad->ncur++; + nad->nss[ns].next = nad->elems[elem].ns; + nad->elems[elem].ns = ns; + + nad->nss[ns].luri = strlen(uri); + nad->nss[ns].iuri = _nad_cdata(nad, uri, nad->nss[ns].luri); + if(prefix != NULL) + { + nad->nss[ns].lprefix = strlen(prefix); + nad->nss[ns].iprefix = _nad_cdata(nad, prefix, nad->nss[ns].lprefix); + } + else + nad->nss[ns].iprefix = -1; + + return ns; +} + +void _nad_escape(nad_t nad, int data, int len, int flag) +{ + char *c; + int ic; + + if(len <= 0) return; + + /* first, if told, find and escape ' */ + while(flag >= 3 && (c = memchr(nad->cdata + data,'\'',len)) != NULL) + { + /* get offset */ + ic = c - nad->cdata; + + /* cute, eh? handle other data before this normally */ + _nad_escape(nad, data, ic - data, 2); + + /* ensure enough space, and add our escaped ' */ + NAD_SAFE(nad->cdata, nad->ccur + 6, nad->clen); + memcpy(nad->cdata + nad->ccur, "'", 6); + nad->ccur += 6; + + /* just update and loop for more */ + len -= (ic+1) - data; + data = ic+1; + } + + /* next look for < */ + while(flag >= 2 && (c = memchr(nad->cdata + data,'<',len)) != NULL) + { + ic = c - nad->cdata; + _nad_escape(nad, data, ic - data, 1); + + /* ensure enough space, and add our escaped < */ + NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen); + memcpy(nad->cdata + nad->ccur, "<", 4); + nad->ccur += 4; + + /* just update and loop for more */ + len -= (ic+1) - data; + data = ic+1; + } + + /* next look for > */ + while(flag >= 1 && (c = memchr(nad->cdata + data, '>', len)) != NULL) + { + ic = c - nad->cdata; + _nad_escape(nad, data, ic - data, 0); + + /* ensure enough space, and add our escaped > */ + NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen); + memcpy(nad->cdata + nad->ccur, ">", 4); + nad->ccur += 4; + + /* just update and loop for more */ + len -= (ic+1) - data; + data = ic+1; + } + + /* if & is found, escape it */ + while((c = memchr(nad->cdata + data,'&',len)) != NULL) + { + ic = c - nad->cdata; + + /* ensure enough space */ + NAD_SAFE(nad->cdata, nad->ccur + 5 + (ic - data), nad->clen); + + /* handle normal data */ + memcpy(nad->cdata + nad->ccur, nad->cdata + data, (ic - data)); + nad->ccur += (ic - data); + + /* append escaped < */ + memcpy(nad->cdata + nad->ccur, "&", 5); + nad->ccur += 5; + + /* just update and loop for more */ + len -= (ic+1) - data; + data = ic+1; + } + + /* nothing exciting, just append normal cdata */ + if(len > 0) { + NAD_SAFE(nad->cdata, nad->ccur + len, nad->clen); + memcpy(nad->cdata + nad->ccur, nad->cdata + data, len); + nad->ccur += len; + } +} + +/** internal recursive printing function */ +int _nad_lp0(nad_t nad, int elem) +{ + int attr; + int ndepth; + int ns; + + /* there's a lot of code in here, but don't let that scare you, it's just duplication in order to be a bit more efficient cpu-wise */ + + /* this whole thing is in a big loop for processing siblings */ + while(elem != nad->ecur) + { + + /* make enough space for the opening element */ + ns = nad->elems[elem].my_ns; + if(ns >= 0 && nad->nss[ns].iprefix >= 0) + { + NAD_SAFE(nad->cdata, nad->ccur + nad->elems[elem].lname + nad->nss[ns].lprefix + 2, nad->clen); + } else { + NAD_SAFE(nad->cdata, nad->ccur + nad->elems[elem].lname + 1, nad->clen); + } + + /* opening tag */ + *(nad->cdata + nad->ccur++) = '<'; + + /* add the prefix if necessary */ + if(ns >= 0 && nad->nss[ns].iprefix >= 0) + { + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix); + nad->ccur += nad->nss[ns].lprefix; + *(nad->cdata + nad->ccur++) = ':'; + } + + /* copy in the name */ + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname); + nad->ccur += nad->elems[elem].lname; + + /* add the namespaces */ + for(ns = nad->elems[elem].ns; ns >= 0; ns = nad->nss[ns].next) + { + /* never explicitly declare the implicit xml namespace */ + if(nad->nss[ns].luri == strlen(uri_XML) && strncmp(uri_XML, nad->cdata + nad->nss[ns].iuri, nad->nss[ns].luri) == 0) + continue; + + /* make space */ + if(nad->nss[ns].iprefix >= 0) + { + NAD_SAFE(nad->cdata, nad->ccur + nad->nss[ns].luri + nad->nss[ns].lprefix + 10, nad->clen); + } else { + NAD_SAFE(nad->cdata, nad->ccur + nad->nss[ns].luri + 9, nad->clen); + } + + /* start */ + memcpy(nad->cdata + nad->ccur, " xmlns", 6); + nad->ccur += 6; + + /* prefix if necessary */ + if(nad->nss[ns].iprefix >= 0) + { + *(nad->cdata + nad->ccur++) = ':'; + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix); + nad->ccur += nad->nss[ns].lprefix; + } + + *(nad->cdata + nad->ccur++) = '='; + *(nad->cdata + nad->ccur++) = '\''; + + /* uri */ + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iuri, nad->nss[ns].luri); + nad->ccur += nad->nss[ns].luri; + + *(nad->cdata + nad->ccur++) = '\''; + } + + for(attr = nad->elems[elem].attr; attr >= 0; attr = nad->attrs[attr].next) + { + if(nad->attrs[attr].lname <= 0) continue; + + /* make enough space for the wrapper part */ + ns = nad->attrs[attr].my_ns; + if(ns >= 0 && nad->nss[ns].iprefix >= 0) + { + NAD_SAFE(nad->cdata, nad->ccur + nad->attrs[attr].lname + nad->nss[ns].lprefix + 4, nad->clen); + } else { + NAD_SAFE(nad->cdata, nad->ccur + nad->attrs[attr].lname + 3, nad->clen); + } + + *(nad->cdata + nad->ccur++) = ' '; + + /* add the prefix if necessary */ + if(ns >= 0 && nad->nss[ns].iprefix >= 0) + { + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix); + nad->ccur += nad->nss[ns].lprefix; + *(nad->cdata + nad->ccur++) = ':'; + } + + /* copy in the name parts */ + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->attrs[attr].iname, nad->attrs[attr].lname); + nad->ccur += nad->attrs[attr].lname; + *(nad->cdata + nad->ccur++) = '='; + *(nad->cdata + nad->ccur++) = '\''; + + /* copy in the escaped value */ + _nad_escape(nad, nad->attrs[attr].ival, nad->attrs[attr].lval, 3); + + /* make enough space for the closing quote and add it */ + NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen); + *(nad->cdata + nad->ccur++) = '\''; + } + + /* figure out what's next */ + if(elem+1 == nad->ecur) + ndepth = -1; + else + ndepth = nad->elems[elem+1].depth; + + /* handle based on if there are children, update nelem after done */ + if(ndepth <= nad->elems[elem].depth) + { + /* make sure there's enough for what we could need */ + NAD_SAFE(nad->cdata, nad->ccur + 2, nad->clen); + if(nad->elems[elem].lcdata == 0) + { + memcpy(nad->cdata + nad->ccur, "/>", 2); + nad->ccur += 2; + }else{ + *(nad->cdata + nad->ccur++) = '>'; + + /* copy in escaped cdata */ + _nad_escape(nad, nad->elems[elem].icdata, nad->elems[elem].lcdata,2); + + /* make room */ + ns = nad->elems[elem].my_ns; + if(ns >= 0 && nad->nss[ns].iprefix >= 0) + { + NAD_SAFE(nad->cdata, nad->ccur + 4 + nad->elems[elem].lname + nad->nss[ns].lprefix, nad->clen); + } else { + NAD_SAFE(nad->cdata, nad->ccur + 3 + nad->elems[elem].lname, nad->clen); + } + + /* close tag */ + memcpy(nad->cdata + nad->ccur, "ccur += 2; + + /* add the prefix if necessary */ + if(ns >= 0 && nad->nss[ns].iprefix >= 0) + { + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix); + nad->ccur += nad->nss[ns].lprefix; + *(nad->cdata + nad->ccur++) = ':'; + } + + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname); + nad->ccur += nad->elems[elem].lname; + *(nad->cdata + nad->ccur++) = '>'; + } + + /* always try to append the tail */ + _nad_escape(nad, nad->elems[elem].itail, nad->elems[elem].ltail,2); + + /* if no siblings either, bail */ + if(ndepth < nad->elems[elem].depth) + return elem+1; + + /* next sibling */ + elem++; + }else{ + int nelem; + /* process any children */ + + /* close ourself and append any cdata first */ + NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen); + *(nad->cdata + nad->ccur++) = '>'; + _nad_escape(nad, nad->elems[elem].icdata, nad->elems[elem].lcdata,2); + + /* process children */ + nelem = _nad_lp0(nad,elem+1); + + /* close and tail up */ + ns = nad->elems[elem].my_ns; + if(ns >= 0 && nad->nss[ns].iprefix >= 0) + { + NAD_SAFE(nad->cdata, nad->ccur + 4 + nad->elems[elem].lname + nad->nss[ns].lprefix, nad->clen); + } else { + NAD_SAFE(nad->cdata, nad->ccur + 3 + nad->elems[elem].lname, nad->clen); + } + memcpy(nad->cdata + nad->ccur, "ccur += 2; + if(ns >= 0 && nad->nss[ns].iprefix >= 0) + { + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix); + nad->ccur += nad->nss[ns].lprefix; + *(nad->cdata + nad->ccur++) = ':'; + } + memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname); + nad->ccur += nad->elems[elem].lname; + *(nad->cdata + nad->ccur++) = '>'; + _nad_escape(nad, nad->elems[elem].itail, nad->elems[elem].ltail,2); + + /* if the next element is not our sibling, we're done */ + if(nelem < nad->ecur && nad->elems[nelem].depth < nad->elems[elem].depth) + return nelem; + + /* for next sibling in while loop */ + elem = nelem; + } + + /* here's the end of that big while loop */ + } + + return elem; +} + +void nad_print(nad_t nad, int elem, char **xml, int *len) +{ + int ixml = nad->ccur; + + _nad_ptr_check(__func__, nad); + + _nad_lp0(nad,elem); + *len = nad->ccur - ixml; + *xml = nad->cdata + ixml; +} + +/** + * nads serialize to a buffer of this form: + * + * [buflen][ecur][acur][ncur][ccur][elems][attrs][nss][cdata] + * + * nothing is done with endianness or word length, so the nad must be + * serialized and deserialized on the same platform + * + * buflen is not actually used by deserialize(), but is provided as a + * convenience to the application so it knows how many bytes to read before + * passing them in to deserialize() + * + * the depths array is not stored, so after deserialization + * nad_append_elem() and nad_append_cdata() will not work. this is rarely + * a problem + */ + +void nad_serialize(nad_t nad, char **buf, int *len) { + char *pos; + + _nad_ptr_check(__func__, nad); + + *len = sizeof(int) * 5 + /* 4 ints in nad_t, plus one for len */ + sizeof(struct nad_elem_st) * nad->ecur + + sizeof(struct nad_attr_st) * nad->acur + + sizeof(struct nad_ns_st) * nad->ncur + + sizeof(char) * nad->ccur; + + *buf = (char *) malloc(*len); + pos = *buf; + + * (int *) pos = *len; pos += sizeof(int); + * (int *) pos = nad->ecur; pos += sizeof(int); + * (int *) pos = nad->acur; pos += sizeof(int); + * (int *) pos = nad->ncur; pos += sizeof(int); + * (int *) pos = nad->ccur; pos += sizeof(int); + + memcpy(pos, nad->elems, sizeof(struct nad_elem_st) * nad->ecur); pos += sizeof(struct nad_elem_st) * nad->ecur; + memcpy(pos, nad->attrs, sizeof(struct nad_attr_st) * nad->acur); pos += sizeof(struct nad_attr_st) * nad->acur; + memcpy(pos, nad->nss, sizeof(struct nad_ns_st) * nad->ncur); pos += sizeof(struct nad_ns_st) * nad->ncur; + memcpy(pos, nad->cdata, sizeof(char) * nad->ccur); +} + +nad_t nad_deserialize(nad_cache_t cache, const char *buf) { + nad_t nad = nad_new(cache); + const char *pos = buf + sizeof(int); /* skip len */ + + _nad_ptr_check(__func__, nad); + + nad->ecur = * (int *) pos; pos += sizeof(int); + nad->acur = * (int *) pos; pos += sizeof(int); + nad->ncur = * (int *) pos; pos += sizeof(int); + nad->ccur = * (int *) pos; pos += sizeof(int); + nad->elen = nad->ecur; + nad->alen = nad->acur; + nad->nlen = nad->ncur; + nad->clen = nad->ccur; + + if(nad->ecur > 0) + { + nad->elems = (struct nad_elem_st *) malloc(sizeof(struct nad_elem_st) * nad->ecur); + memcpy(nad->elems, pos, sizeof(struct nad_elem_st) * nad->ecur); + pos += sizeof(struct nad_elem_st) * nad->ecur; + } + + if(nad->acur > 0) + { + nad->attrs = (struct nad_attr_st *) malloc(sizeof(struct nad_attr_st) * nad->acur); + memcpy(nad->attrs, pos, sizeof(struct nad_attr_st) * nad->acur); + pos += sizeof(struct nad_attr_st) * nad->acur; + } + + if(nad->ncur > 0) + { + nad->nss = (struct nad_ns_st *) malloc(sizeof(struct nad_ns_st) * nad->ncur); + memcpy(nad->nss, pos, sizeof(struct nad_ns_st) * nad->ncur); + pos += sizeof(struct nad_ns_st) * nad->ncur; + } + + if(nad->ccur > 0) + { + nad->cdata = (char *) malloc(sizeof(char) * nad->ccur); + memcpy(nad->cdata, pos, sizeof(char) * nad->ccur); + } + + return nad; +} + +#ifdef HAVE_EXPAT + +/** parse a buffer into a nad */ + +struct build_data { + nad_t nad; + int depth; +}; + +static void _nad_parse_element_start(void *arg, const char *name, const char **atts) { + struct build_data *bd = (struct build_data *) arg; + char buf[1024]; + char *uri, *elem, *prefix; + const char **attr; + int ns; + + /* make a copy */ + strncpy(buf, name, 1024); + buf[1023] = '\0'; + + /* expat gives us: + prefixed namespaced elem: uri|elem|prefix + default namespaced elem: uri|elem + un-namespaced elem: elem + */ + + /* extract all the bits */ + uri = buf; + elem = strchr(uri, '|'); + if(elem != NULL) { + *elem = '\0'; + elem++; + prefix = strchr(elem, '|'); + if(prefix != NULL) { + *prefix = '\0'; + prefix++; + } + ns = nad_add_namespace(bd->nad, uri, prefix); + } else { + /* un-namespaced, just take it as-is */ + uri = NULL; + elem = buf; + prefix = NULL; + ns = -1; + } + + /* add it */ + nad_append_elem(bd->nad, ns, elem, bd->depth); + + /* now the attributes, one at a time */ + attr = atts; + while(attr[0] != NULL) { + + /* make a copy */ + strncpy(buf, attr[0], 1024); + buf[1023] = '\0'; + + /* extract all the bits */ + uri = buf; + elem = strchr(uri, '|'); + if(elem != NULL) { + *elem = '\0'; + elem++; + prefix = strchr(elem, '|'); + if(prefix != NULL) { + *prefix = '\0'; + prefix++; + } + ns = nad_add_namespace(bd->nad, uri, prefix); + } else { + /* un-namespaced, just take it as-is */ + uri = NULL; + elem = buf; + prefix = NULL; + ns = -1; + } + + /* add it */ + nad_append_attr(bd->nad, ns, elem, (char *) attr[1]); + + attr += 2; + } + + bd->depth++; +} + +static void _nad_parse_element_end(void *arg, const char *name) { + struct build_data *bd = (struct build_data *) arg; + + bd->depth--; +} + +static void _nad_parse_cdata(void *arg, const char *str, int len) { + struct build_data *bd = (struct build_data *) arg; + + /* go */ + nad_append_cdata(bd->nad, (char *) str, len, bd->depth); +} + +static void _nad_parse_namespace_start(void *arg, const char *prefix, const char *uri) { + struct build_data *bd = (struct build_data *) arg; + int ns; + + ns = nad_add_namespace(bd->nad, (char *) uri, (char *) prefix); + + /* Always set the namespace (to catch cases where nad_add_namespace doesn't add it) */ + bd->nad->scope = ns; +} + +nad_t nad_parse(nad_cache_t cache, const char *buf, int len) { + struct build_data bd; + XML_Parser p; + + if(len == 0) + len = strlen(buf); + + p = XML_ParserCreateNS(NULL, '|'); + if(p == NULL) + return NULL; + + XML_SetReturnNSTriplet(p, 1); + + bd.nad = nad_new(cache); + bd.depth = 0; + + XML_SetUserData(p, (void *) &bd); + XML_SetElementHandler(p, _nad_parse_element_start, _nad_parse_element_end); + XML_SetCharacterDataHandler(p, _nad_parse_cdata); + XML_SetStartNamespaceDeclHandler(p, _nad_parse_namespace_start); + + if(!XML_Parse(p, buf, len, 1)) { + XML_ParserFree(p); + nad_free(bd.nad); + return NULL; + } + + XML_ParserFree(p); + + if(bd.depth != 0) + return NULL; + + return bd.nad; +} + +#endif diff --git a/util/nad.h b/util/nad.h new file mode 100644 index 00000000..279476f7 --- /dev/null +++ b/util/nad.h @@ -0,0 +1,173 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/nad.h + * @brief Not A DOM + * @author Jeremie Miller + * @author Robert Norris + * $Date: 2004/05/05 23:49:38 $ + * $Revision: 1.3 $ + * + * NAD is very simplistic, and requires all string handling to use a length. + * Apps using this must be aware of the structure and access it directly for + * most information. NADs can only be built by successively using the _append_ + * functions correctly. After built, they can be modified using other + * functions, or by direct access. To access cdata on an elem or attr, use + * nad->cdata + nad->xxx[index].ixxx for the start, and .lxxx for len. + * + * Namespace support seems to work, but hasn't been thoroughly tested. in + * particular, editing the NAD after its creation might have quirks. use at + * your own risk! Note that nad_add_namespace() brings a namespace into scope + * for the next element added with nad_append_elem(), nad_insert_elem() or + * nad_wrap_elem() (and by extension, any of its subelements). This is the + * same way that Expat does things, so nad_add_namespace() can be driven from + * Expat's StartNamespaceDeclHandler. See nad_parse() for an example of how to + * use Expat to drive NAD. + */ + +#ifndef INCL_UTIL_NAD_H +#define INCL_UTIL_NAD_H 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* !!! if configure ever checks for expat, then remove this */ +#ifndef HAVE_EXPAT +# define HAVE_EXPAT 1 +#endif + +#include "pool.h" + +struct nad_elem_st { + int parent; + int iname, lname; + int icdata, lcdata; /* cdata within this elem (up to first child) */ + int itail, ltail; /* cdata after this elem */ + int attr; + int ns; + int my_ns; + int depth; +}; + +struct nad_attr_st { + int iname, lname; + int ival, lval; + int my_ns; + int next; +}; + +struct nad_ns_st { + int iuri, luri; + int iprefix, lprefix; + int next; +}; + +typedef struct _nad_st { + pool_t p; + struct nad_elem_st *elems; + struct nad_attr_st *attrs; + struct nad_ns_st *nss; + char *cdata; + int *depths; /* for tracking the last elem at a depth */ + int elen, alen, nlen, clen, dlen; + int ecur, acur, ncur, ccur; + int scope; /* currently scoped namespaces, get attached to the next element */ +} *nad_t; + +/** create a new nad */ +nad_t nad_new(pool_t p); + +/** copy a nad */ +nad_t nad_copy(nad_t nad, pool_t p); + +/** find the next element with this name/depth */ +/** 0 for siblings, 1 for children and so on */ +int nad_find_elem(nad_t nad, int elem, int ns, const char *name, int depth); + +/** find the first matching attribute (and optionally value) */ +int nad_find_attr(nad_t nad, int elem, int ns, const char *name, const char *val); + +/** find the first matching namespace (and optionally prefix) */ +int nad_find_namespace(nad_t nad, int elem, const char *uri, const char *prefix); + +/** find a namespace in scope (and optionally prefix) */ +int nad_find_scoped_namespace(nad_t nad, const char *uri, const char *prefix); + +/** reset or store the given attribute */ +void nad_set_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen); + +/** insert and return a new element as a child of this one */ +int nad_insert_elem(nad_t nad, int elem, int ns, const char *name, const char *cdata); + +/** remove an element (and its subelements) */ +void nad_drop_elem(nad_t nad, int elem); + +/** wrap an element with another element */ +void nad_wrap_elem(nad_t nad, int elem, int ns, const char *name); + +/** insert part of a nad into another nad */ +int nad_insert_nad(nad_t dest, int delem, nad_t src, int selem); + +/** append and return a new element */ +int nad_append_elem(nad_t nad, int ns, const char *name, int depth); + +/** append attribs to the last element */ +int nad_append_attr(nad_t nad, int ns, const char *name, const char *val); + +/** append more cdata to the last element */ +void nad_append_cdata(nad_t nad, const char *cdata, int len, int depth); + +/** add a namespace to the next element (ie, called when the namespace comes into scope) */ +int nad_add_namespace(nad_t nad, const char *uri, const char *prefix); + +/** declare a namespace on an already existing element */ +int nad_append_namespace(nad_t nad, int elem, const char *uri, const char *prefix); + +/** create a string representation of the given element (and children), point references to it */ +void nad_print(nad_t nad, int elem, char **xml, int *len); + +/** serialize and deserialize a nad */ +void nad_serialize(nad_t nad, char **buf, int *len); +nad_t nad_deserialize(pool_t p, const char *buf); + +#ifdef HAVE_EXPAT +/** create a nad from raw xml */ +nad_t nad_parse(pool_t p, const char *buf, int len); +#endif + +/* these are some helpful macros */ +#define NAD_ENAME(N,E) (N->cdata + N->elems[E].iname) +#define NAD_ENAME_L(N,E) (N->elems[E].lname) +#define NAD_CDATA(N,E) (N->cdata + N->elems[E].icdata) +#define NAD_CDATA_L(N,E) (N->elems[E].lcdata) +#define NAD_ANAME(N,A) (N->cdata + N->attrs[A].iname) +#define NAD_ANAME_L(N,A) (N->attrs[A].lname) +#define NAD_AVAL(N,A) (N->cdata + N->attrs[A].ival) +#define NAD_AVAL_L(N,A) (N->attrs[A].lval) +#define NAD_NURI(N,NS) (N->cdata + N->nss[NS].iuri) +#define NAD_NURI_L(N,NS) (N->nss[NS].luri) +#define NAD_NPREFIX(N,NS) (N->cdata + N->nss[NS].iprefix) +#define NAD_NPREFIX_L(N,NS) (N->nss[NS].lprefix) + +#define NAD_ENS(N,E) (N->elems[E].my_ns) +#define NAD_ANS(N,A) (N->attrs[A].my_ns) + +#endif diff --git a/util/pool.c b/util/pool.c new file mode 100644 index 00000000..803060cb --- /dev/null +++ b/util/pool.c @@ -0,0 +1,289 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "util.h" + + +#ifdef POOL_DEBUG +int pool__total = 0; +int pool__ltotal = 0; +xht pool__disturbed = NULL; +void *_pool__malloc(size_t size) +{ + pool__total++; + return malloc(size); +} +void _pool__free(void *block) +{ + pool__total--; + free(block); +} +#else +#define _pool__malloc malloc +#define _pool__free free +#endif + + +/** make an empty pool */ +pool _pool_new(char *zone, int line) +{ + pool p; + while((p = _pool__malloc(sizeof(_pool))) == NULL) sleep(1); + p->cleanup = NULL; + p->heap = NULL; + p->size = 0; + +#ifdef POOL_DEBUG + p->lsize = -1; + p->zone[0] = '\0'; + snprintf(p->zone, sizeof(p->zone), "%s:%i", zone, line); + sprintf(p->name,"%X",(int)p); + + if(pool__disturbed == NULL) + { + pool__disturbed = (xht)1; /* reentrancy flag! */ + pool__disturbed = xhash_new(POOL_NUM); + } + if(pool__disturbed != (xht)1) + xhash_put(pool__disturbed,p->name,p); +#endif + + return p; +} + +/** free a heap */ +void _pool_heap_free(void *arg) +{ + struct pheap *h = (struct pheap *)arg; + + _pool__free(h->block); + _pool__free(h); +} + +/** mem should always be freed last */ +void _pool_cleanup_append(pool p, struct pfree *pf) +{ + struct pfree *cur; + + if(p->cleanup == NULL) + { + p->cleanup = pf; + p->cleanup_tail = pf; + return; + } + + /* append at end of list */ + cur = p->cleanup_tail; + cur->next = pf; + p->cleanup_tail = pf; +} + +/** create a cleanup tracker */ +struct pfree *_pool_free(pool p, pool_cleaner f, void *arg) +{ + struct pfree *ret; + + /* make the storage for the tracker */ + while((ret = _pool__malloc(sizeof(struct pfree))) == NULL) sleep(1); + ret->f = f; + ret->arg = arg; + ret->next = NULL; + + return ret; +} + +/** create a heap and make sure it get's cleaned up */ +struct pheap *_pool_heap(pool p, int size) +{ + struct pheap *ret; + struct pfree *clean; + + /* make the return heap */ + while((ret = _pool__malloc(sizeof(struct pheap))) == NULL) sleep(1); + while((ret->block = _pool__malloc(size)) == NULL) sleep(1); + ret->size = size; + p->size += size; + ret->used = 0; + + /* append to the cleanup list */ + clean = _pool_free(p, _pool_heap_free, (void *)ret); + clean->heap = ret; /* for future use in finding used mem for pstrdup */ + _pool_cleanup_append(p, clean); + + return ret; +} + +pool _pool_new_heap(int size, char *zone, int line) +{ + pool p; + p = _pool_new(zone, line); + p->heap = _pool_heap(p,size); + return p; +} + +void *pmalloc(pool p, int size) +{ + void *block; + + if(p == NULL) + { + fprintf(stderr,"Memory Leak! [pmalloc received NULL pool, unable to track allocation, exiting]\n"); + abort(); + } + + /* if there is no heap for this pool or it's a big request, just raw, I like how we clean this :) */ + if(p->heap == NULL || size > (p->heap->size / 2)) + { + while((block = _pool__malloc(size)) == NULL) sleep(1); + p->size += size; + _pool_cleanup_append(p, _pool_free(p, _pool__free, block)); + return block; + } + + /* we have to preserve boundaries, long story :) */ + if(size >= 4) + while(p->heap->used&7) p->heap->used++; + + /* if we don't fit in the old heap, replace it */ + if(size > (p->heap->size - p->heap->used)) + p->heap = _pool_heap(p, p->heap->size); + + /* the current heap has room */ + block = (char *)p->heap->block + p->heap->used; + p->heap->used += size; + return block; +} + +void *pmalloc_x(pool p, int size, char c) +{ + void* result = pmalloc(p, size); + if (result != NULL) + memset(result, c, size); + return result; +} + +/** easy safety utility (for creating blank mem for structs, etc) */ +void *pmalloco(pool p, int size) +{ + void *block = pmalloc(p, size); + memset(block, 0, size); + return block; +} + +/** XXX efficient: move this to const char * and then loop throug the existing heaps to see if src is within a block in this pool */ +char *pstrdup(pool p, const char *src) +{ + char *ret; + + if(src == NULL) + return NULL; + + ret = pmalloc(p,strlen(src) + 1); + strcpy(ret,src); + + return ret; +} + +/** use given size */ +char *pstrdupx(pool p, const char *src, int len) +{ + char *ret; + + if(src == NULL || len <= 0) + return NULL; + + ret = pmalloc(p,len + 1); + memcpy(ret,src,len); + ret[len] = '\0'; + + return ret; +} + +int pool_size(pool p) +{ + if(p == NULL) return 0; + + return p->size; +} + +void pool_free(pool p) +{ + struct pfree *cur, *stub; + + if(p == NULL) return; + + cur = p->cleanup; + while(cur != NULL) + { + (*cur->f)(cur->arg); + stub = cur->next; + _pool__free(cur); + cur = stub; + } + +#ifdef POOL_DEBUG + if (pool__disturbed != NULL && pool__disturbed != (xht)1) + xhash_zap(pool__disturbed,p->name); +#endif + + _pool__free(p); + +} + +/** public cleanup utils, insert in a way that they are run FIFO, before mem frees */ +void pool_cleanup(pool p, pool_cleaner f, void *arg) +{ + struct pfree *clean; + + clean = _pool_free(p, f, arg); + clean->next = p->cleanup; + p->cleanup = clean; +} + +#ifdef POOL_DEBUG +void _pool_stat(xht h, const char *key, void *val, void *arg) +{ + pool p = (pool)val; + + if(p->lsize == -1) + fprintf(stderr, "POOL: %s: %s is a new pool\n",p->zone,p->name); + else if(p->size > p->lsize) + fprintf(stderr, "POOL: %s: %s grew %d\n",p->zone,p->name, p->size - p->lsize); + else if((int)arg) + fprintf(stderr, "POOL: %s: %s exists %d\n",p->zone,p->name, p->size); + p->lsize = p->size; +} + +void pool_stat(int full) +{ + if (pool__disturbed == NULL || pool__disturbed == (xht)1) + return; + xhash_walk(pool__disturbed,_pool_stat,(void *)full); + if(pool__total != pool__ltotal) + fprintf(stderr, "POOL: %d total missed mallocs\n",pool__total); + pool__ltotal = pool__total; + return; +} +#else +void pool_stat(int full) +{ + return; +} +#endif diff --git a/util/pool.h b/util/pool.h new file mode 100644 index 00000000..8d2e591f --- /dev/null +++ b/util/pool.h @@ -0,0 +1,62 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/pool.h + * @brief memory pools + * $Revision: 1.2 $ + * $Date: 2004/05/05 23:49:38 $ + */ + +#ifndef INCL_UTIL_POOL_H +#define INCL_UTIL_POOL_H 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* opaque decl */ +typedef struct _pool_st *pool_t; + +typedef void (*pool_cleanup_t)(void *arg); + + +#ifdef POOL_DEBUG +# define pool_new() _pool_new(__FILE__,__LINE__) +# define pool_heap(i) _pool_new_heap(i,__FILE__,__LINE__) +#else +# define pool_heap(i) _pool_new_heap(i,NULL,0) +# define pool_new() _pool_new(NULL,0) +#endif + +pool_t _pool_new(char *file, int line); /* new pool_t :) */ +pool_t _pool_new_heap(int size, char *file, int line); /* creates a new memory pool_t with an initial heap size */ +void *pmalloc(pool_t p, int size); /* wrapper around malloc, takes from the pool, cleaned up automatically */ +void *pmalloc_x(pool_t p, int size, char c); /* Wrapper around pmalloc which prefils buffer with c */ +void *pmalloco(pool_t p, int size); /* YAPW for zeroing the block */ +char *pstrdup(pool_t p, const char *src); /* wrapper around strdup, gains mem from pool_t */ +void pool_stat(int full); /* print to stderr the changed pools and reset */ +char *pstrdupx(pool_t p, const char *src, int len); /* use given len */ +void pool_cleanup(pool_t p, pool_cleanup_t fn, void *arg); /* calls f(arg) before the pool_t is freed during cleanup */ +void pool_clear(pool_t p); +void pool_free(pool_t p); /* calls the cleanup functions, frees all the data on the pool, and deletes the pool_t itself */ +int pool_size(pool_t p); /* returns total bytes allocated in this pool_t */ + + +#endif diff --git a/util/pqueue.c b/util/pqueue.c new file mode 100644 index 00000000..c54763d9 --- /dev/null +++ b/util/pqueue.c @@ -0,0 +1,144 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* priority queues */ + +#include "pqueue.h" + +#include "pool.h" + +#include /* to get NULL */ +#include + +typedef struct _pqueue_node_st *_pqueue_node_t; +struct _pqueue_node_st { + void *data; + + int priority; + + _pqueue_node_t next; + _pqueue_node_t prev; +}; + +struct _pqueue_st { + pool_t p; + _pqueue_node_t cache; + + _pqueue_node_t front; + _pqueue_node_t back; + + int size; +}; + +pqueue_t pqueue_new(pool_t p) { + pqueue_t q; + + q = (pqueue_t) pmalloco(p, sizeof(struct _pqueue_st)); + + q->p = p; + + return q; +} + +void pqueue_push(pqueue_t q, void *data, int priority) { + _pqueue_node_t qn, scan; + + assert((int) q); + + q->size++; + + /* node from the cache, or make a new one */ + qn = q->cache; + if(qn != NULL) + q->cache = qn->next; + else + qn = (_pqueue_node_t) pmalloc(q->p, sizeof(struct _pqueue_node_st)); + + qn->data = data; + qn->priority = priority; + + qn->next = NULL; + qn->prev = NULL; + + /* first one */ + if(q->back == NULL && q->front == NULL) { + q->back = qn; + q->front = qn; + + return; + } + + /* find the first node with priority <= to us */ + for(scan = q->back; scan != NULL && scan->priority > priority; scan = scan->next); + + /* didn't find one, so we have top priority - push us on the front */ + if(scan == NULL) { + qn->prev = q->front; + qn->prev->next = qn; + q->front = qn; + + return; + } + + /* push us in front of scan */ + qn->next = scan; + qn->prev = scan->prev; + + if(scan->prev != NULL) + scan->prev->next = qn; + else + q->back = qn; + + scan->prev = qn; +} + +void *pqueue_pull(pqueue_t q) { + void *data; + _pqueue_node_t qn; + + assert((int) q); + + if(q->front == NULL) + return NULL; + + data = q->front->data; + + qn = q->front; + + if(qn->prev != NULL) + qn->prev->next = NULL; + + q->front = qn->prev; + + /* node to cache for later reuse */ + qn->next = q->cache; + q->cache = qn; + + if(q->front == NULL) + q->back = NULL; + + q->size--; + + return data; +} + +int pqueue_size(pqueue_t q) { + return q->size; +} diff --git a/util/pqueue.h b/util/pqueue.h new file mode 100644 index 00000000..dc285611 --- /dev/null +++ b/util/pqueue.h @@ -0,0 +1,41 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/pqueue.h + * @brief priority queues + * @author Robert Norris + * $Date: 2004/05/05 23:49:38 $ + * $Revision: 1.1 $ + */ + +#ifndef INCL_UTIL_PQUEUE_H +#define INCL_UTIL_PQUEUE_H 1 + +#include "pool.h" + +/* opaque decl */ +typedef struct _pqueue_st *pqueue_t; + +pqueue_t pqueue_new(pool_t p); +void pqueue_push(pqueue_t q, void *data, int pri); +void *pqueue_pull(pqueue_t q); +int pqueue_size(pqueue_t q); + +#endif diff --git a/util/rate.c b/util/rate.c new file mode 100644 index 00000000..8c175a40 --- /dev/null +++ b/util/rate.c @@ -0,0 +1,108 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* rate controls (for implementing connect-limiting or karma) */ + +#include "util.h" + +rate_t rate_new(int total, int seconds, int wait) +{ + rate_t rt = (rate_t) malloc(sizeof(struct rate_st)); + memset(rt, 0, sizeof(struct rate_st)); + + rt->total = total; + rt->seconds = seconds; + rt->wait = wait; + + return rt; +} + +void rate_free(rate_t rt) +{ + free(rt); +} + +void rate_reset(rate_t rt) +{ + rt->time = 0; + rt->count = 0; + rt->bad = 0; +} + +void rate_add(rate_t rt, int count) +{ + rt->count += count; + + /* first event, so set the time */ + if(rt->time == 0) + rt->time = time(NULL); + + /* uhoh, they stuffed up */ + if(rt->count >= rt->total) + rt->bad = time(NULL); +} + +int rate_left(rate_t rt) +{ + /* if we're bad, then there's none left */ + if(rt->bad != 0) + return 0; + + return rt->total - rt->count; +} + +int rate_check(rate_t rt) +{ + time_t now; + + /* not tracking */ + if(rt->time == 0) + return 1; + + /* under the limit */ + if(rt->count < rt->total) + return 1; + + now = time(NULL); + + /* currently bad */ + if(rt->bad != 0) + { + /* wait over, they're good again */ + if(now - rt->bad >= rt->wait) + { + rate_reset(rt); + return 1; + } + + /* keep them waiting */ + return 0; + } + + /* rate expired */ + if(time(NULL) - rt->time >= rt->seconds) + { + rate_reset(rt); + return 1; + } + + /* they're inside the time, and not bad yet */ + return 1; +} diff --git a/util/serial.c b/util/serial.c new file mode 100644 index 00000000..f210827d --- /dev/null +++ b/util/serial.c @@ -0,0 +1,144 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* these are useful utilities for data serialisation */ + +#include "util.h" + +/* + * ser_string_get() and ser_int_get() retrieve a string (null-terminated) or + * an int (sizeof(int) chars) from source, and store it in dest. source is a + * pointer into buf, and will be updated before the call returns. + * buf is a pointer to the start of the source buffer, and len is the length + * of the buffer. if retrieving the data would take us pass the end of the + * array, a non-zero value will be returned. if the call succeeds, 0 is + * returned. + */ + +int ser_string_get(char **dest, int *source, const char *buf, int len) +{ + const char *end, *c; + + /* end of the buffer */ + end = buf + ((sizeof(char) * (len - 1))); + + /* make sure we have a \0 before the end of the buffer */ + c = &(buf[*source]); + while(c <= end && *c != '\0') c++; + if(c > end) + /* we ran past the end, fail */ + return 1; + + /* copy the string */ + *dest = strdup(&(buf[*source])); + + /* and move the pointer */ + *source += strlen(*dest) + 1; + + return 0; +} + +int ser_int_get(int *dest, int *source, const char *buf, int len) +{ + union + { + char c[sizeof(int)]; + int i; + } u; + int i; + + /* we need sizeof(int) bytes */ + if(&(buf[*source]) + sizeof(int) > buf + (sizeof(char) * len)) + return 1; + + /* copy the bytes into the union. we do it this way to avoid alignment problems */ + for(i = 0; i < sizeof(int); i++) + { + u.c[i] = buf[*source]; + (*source)++; + } + *dest = u.i; + + return 0; +} + +/* + * ser_string_set() and ser_int_set() stores the string or int referenced by + * source into buf, starting at dest. len holds the current length of the + * buffer. if storing the data would overrun the end of the buffer, the buffer + * will be grown to accomodate. buf, dest and len will be updated. + */ + +/* shamelessy stolen from nad.c */ + +#define BLOCKSIZE 1024 + +/** internal: do and return the math and ensure it gets realloc'd */ +static int _ser_realloc(void **oblocks, int len) +{ + void *nblocks; + int nlen; + + /* round up to standard block sizes */ + nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE; + + /* keep trying till we get it */ + while((nblocks = realloc(*oblocks, nlen)) == NULL) sleep(1); + *oblocks = nblocks; + return nlen; +} + +/** this is the safety check used to make sure there's always enough mem */ +#define SER_SAFE(blocks, size, len) if((size) > len) len = _ser_realloc((void**)&(blocks),(size)); + +void ser_string_set(char *source, int *dest, char **buf, int *len) +{ + int need = sizeof(char) * (strlen(source) + 1); + + /* make more space if necessary */ + SER_SAFE(*buf, *dest + need, *len); + + /* copy it in */ + strcpy(*buf + *dest, source); + + /* and shift the pointer */ + *dest += need; +} + +void ser_int_set(int source, int *dest, char **buf, int *len) +{ + union + { + char c[sizeof(int)]; + int i; + } u; + int i; + + /* make more space if necessary */ + SER_SAFE(*buf, *dest + sizeof(int), *len) + + /* copy it in */ + u.i = source; + for(i = 0; i < sizeof(int); i++) + (*buf)[*dest + i] = u.c[i]; + + /* and shift the pointer */ + *dest += sizeof(int); +} diff --git a/util/sha1.c b/util/sha1.c new file mode 100644 index 00000000..dc4328a6 --- /dev/null +++ b/util/sha1.c @@ -0,0 +1,147 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is SHA 180-1 Reference Implementation (Compact version) + * + * The Initial Developer of the Original Code is Paul Kocher of + * Cryptography Research. Portions created by Paul Kocher are + * Copyright (C) 1995-9 by Cryptography Research, Inc. All + * Rights Reserved. + * + */ + +/* modified for j2 by Robert Norris */ + +#include "sha1.h" +#include + +static void sha1_hashblock(sha1_state_t *ctx); + +void sha1_init(sha1_state_t *ctx) { + int i; + + ctx->lenW = 0; + ctx->sizeHi = ctx->sizeLo = 0; + + /* Initialize H with the magic constants (see FIPS180 for constants) + */ + ctx->H[0] = 0x67452301L; + ctx->H[1] = 0xefcdab89L; + ctx->H[2] = 0x98badcfeL; + ctx->H[3] = 0x10325476L; + ctx->H[4] = 0xc3d2e1f0L; + + for (i = 0; i < 80; i++) + ctx->W[i] = 0; +} + + +void sha1_append(sha1_state_t *ctx, const unsigned char *dataIn, int len) { + int i; + + /* Read the data into W and process blocks as they get full + */ + for (i = 0; i < len; i++) { + ctx->W[ctx->lenW / 4] <<= 8; + ctx->W[ctx->lenW / 4] |= (unsigned long)dataIn[i]; + if ((++ctx->lenW) % 64 == 0) { + sha1_hashblock(ctx); + ctx->lenW = 0; + } + ctx->sizeLo += 8; + ctx->sizeHi += (ctx->sizeLo < 8); + } +} + + +void sha1_finish(sha1_state_t *ctx, unsigned char hashout[20]) { + unsigned char pad0x80 = 0x80; + unsigned char pad0x00 = 0x00; + unsigned char padlen[8]; + int i; + + /* Pad with a binary 1 (e.g. 0x80), then zeroes, then length + */ + padlen[0] = (unsigned char)((ctx->sizeHi >> 24) & 255); + padlen[1] = (unsigned char)((ctx->sizeHi >> 16) & 255); + padlen[2] = (unsigned char)((ctx->sizeHi >> 8) & 255); + padlen[3] = (unsigned char)((ctx->sizeHi >> 0) & 255); + padlen[4] = (unsigned char)((ctx->sizeLo >> 24) & 255); + padlen[5] = (unsigned char)((ctx->sizeLo >> 16) & 255); + padlen[6] = (unsigned char)((ctx->sizeLo >> 8) & 255); + padlen[7] = (unsigned char)((ctx->sizeLo >> 0) & 255); + sha1_append(ctx, &pad0x80, 1); + while (ctx->lenW != 56) + sha1_append(ctx, &pad0x00, 1); + sha1_append(ctx, padlen, 8); + + /* Output hash + */ + for (i = 0; i < 20; i++) { + hashout[i] = (unsigned char)(ctx->H[i / 4] >> 24); + ctx->H[i / 4] <<= 8; + } + + /* + * Re-initialize the context (also zeroizes contents) + */ + sha1_init(ctx); +} + + +void sha1_hash(const unsigned char *dataIn, int len, unsigned char hashout[20]) { + sha1_state_t ctx; + + sha1_init(&ctx); + sha1_append(&ctx, dataIn, len); + sha1_finish(&ctx, hashout); +} + + +#define SHA_ROTL(X,n) ((((X) << (n)) | ((X) >> (32-(n)))) & 0xffffffffL) + +static void sha1_hashblock(sha1_state_t *ctx) { + int t; + unsigned long A,B,C,D,E,TEMP; + + for (t = 16; t <= 79; t++) + ctx->W[t] = + SHA_ROTL(ctx->W[t-3] ^ ctx->W[t-8] ^ ctx->W[t-14] ^ ctx->W[t-16], 1); + + A = ctx->H[0]; + B = ctx->H[1]; + C = ctx->H[2]; + D = ctx->H[3]; + E = ctx->H[4]; + + for (t = 0; t <= 19; t++) { + TEMP = (SHA_ROTL(A,5) + (((C^D)&B)^D) + E + ctx->W[t] + 0x5a827999L) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (t = 20; t <= 39; t++) { + TEMP = (SHA_ROTL(A,5) + (B^C^D) + E + ctx->W[t] + 0x6ed9eba1L) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (t = 40; t <= 59; t++) { + TEMP = (SHA_ROTL(A,5) + ((B&C)|(D&(B|C))) + E + ctx->W[t] + 0x8f1bbcdcL) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (t = 60; t <= 79; t++) { + TEMP = (SHA_ROTL(A,5) + (B^C^D) + E + ctx->W[t] + 0xca62c1d6L) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + + ctx->H[0] += A; + ctx->H[1] += B; + ctx->H[2] += C; + ctx->H[3] += D; + ctx->H[4] += E; +} diff --git a/util/sha1.h b/util/sha1.h new file mode 100644 index 00000000..a62d703e --- /dev/null +++ b/util/sha1.h @@ -0,0 +1,38 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* sha1 functions */ + +#ifndef INCL_SHA1_H +#define INCL_SHA1_H + +typedef struct sha1_state_s { + unsigned long H[5]; + unsigned long W[80]; + int lenW; + unsigned long sizeHi,sizeLo; +} sha1_state_t; + +void sha1_init(sha1_state_t *ctx); +void sha1_append(sha1_state_t *ctx, const unsigned char *dataIn, int len); +void sha1_finish(sha1_state_t *ctx, unsigned char hashout[20]); +void sha1_hash(const unsigned char *dataIn, int len, unsigned char hashout[20]); + +#endif diff --git a/util/stanza.c b/util/stanza.c new file mode 100644 index 00000000..d8040f73 --- /dev/null +++ b/util/stanza.c @@ -0,0 +1,106 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "util.h" + +/* stanza manipulation */ + +typedef struct _stanza_error_st { + char *name; + char *type; + char *code; +} *stanza_error_t; + +/** if you change these, reflect your changes in the defines in util.h */ +static struct _stanza_error_st _stanza_errors[] = { + { "bad-request", "modify", "400" }, /* stanza_err_BAD_REQUEST */ + { "conflict", "cancel", "409" }, /* stanza_err_CONFLICT */ + { "feature-not-implemented", "cancel", "501" }, /* stanza_err_FEATURE_NOT_IMPLEMENTED */ + { "forbidden", "auth", "403" }, /* stanza_err_FORBIDDEN */ + { "gone", "modify", "302" }, /* stanza_err_GONE */ + { "internal-server-error", "wait", "500" }, /* stanza_err_INTERNAL_SERVER_ERROR */ + { "item-not-found", "cancel", "404" }, /* stanza_err_ITEM_NOT_FOUND */ + { "jid-malformed", "modify", "400" }, /* stanza_err_JID_MALFORMED */ + { "not-acceptable", "cancel", "406" }, /* stanza_err_NOT_ACCEPTABLE */ + { "not-allowed", "cancel", "405" }, /* stanza_err_NOT_ALLOWED */ + { "payment-required", "auth", "402" }, /* stanza_err_PAYMENT_REQUIRED */ + { "recipient-unavailable", "wait", "404" }, /* stanza_err_RECIPIENT_UNAVAILABLE */ + { "redirect", "modify", "302" }, /* stanza_err_REDIRECT */ + { "registration-required", "auth", "407" }, /* stanza_err_REGISTRATION_REQUIRED */ + { "remote-server-not-found", "cancel", "404" }, /* stanza_err_REMOTE_SERVER_NOT_FOUND */ + { "remote-server-timeout", "wait", "502" }, /* stanza_err_REMOTE_SERVER_TIMEOUT */ + { "resource-constraint", "wait", "500" }, /* stanza_err_RESOURCE_CONSTRAINT */ + { "service-unavailable", "cancel", "503" }, /* stanza_err_SERVICE_UNAVAILABLE */ + { "subscription-required", "auth", "407" }, /* stanza_err_SUBSCRIPTION_REQUIRED */ + { "undefined-condition", NULL, "500" }, /* stanza_err_UNDEFINED_CONDITION */ + { "unexpected-request", "wait", "400" }, /* stanza_err_UNEXPECTED_REQUEST */ + { NULL, NULL, "401" }, /* stanza_err_OLD_UNAUTH */ + { NULL, NULL, NULL } +}; + +/** error the packet */ +nad_t stanza_error(nad_t nad, int elem, int err) { + int ns; + + assert((int) nad); + assert((int) (elem >= 0)); + assert((int) (err >= 100 && err < stanza_err_LAST)); + + err = err - 100; + + nad_set_attr(nad, elem, -1, "type", "error", 5); + + elem = nad_insert_elem(nad, elem, NAD_ENS(nad, elem), "error", NULL); + if(_stanza_errors[err].code != NULL) + nad_set_attr(nad, elem, -1, "code", _stanza_errors[err].code, 0); + if(_stanza_errors[err].type != NULL) + nad_set_attr(nad, elem, -1, "type", _stanza_errors[err].type, 0); + + if(_stanza_errors[err].name != NULL) { + ns = nad_add_namespace(nad, uri_STANZA_ERR, NULL); + nad_insert_elem(nad, elem, ns, _stanza_errors[err].name, NULL); + } + + return nad; +} + +/** flip the to and from attributes on this elem */ +nad_t stanza_tofrom(nad_t nad, int elem) { + int attr; + char to[3072], from[3072]; + + assert((int) nad); + + to[0] = '\0'; + from[0] = '\0'; + + attr = nad_find_attr(nad, elem, -1, "to", NULL); + if(attr >= 0) + snprintf(to, 3072, "%.*s", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + + attr = nad_find_attr(nad, elem, -1, "from", NULL); + if(attr >= 0) + snprintf(from, 3072, "%.*s", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + + nad_set_attr(nad, elem, -1, "to", from[0] != '\0' ? from : NULL, 0); + nad_set_attr(nad, elem, -1, "from", to[0] != '\0' ? to : NULL, 0); + + return nad; +} diff --git a/util/str.c b/util/str.c new file mode 100644 index 00000000..a3cae5b8 --- /dev/null +++ b/util/str.c @@ -0,0 +1,374 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "util.h" + +char *j_strdup(const char *str) +{ + if(str == NULL) + return NULL; + else + return strdup(str); +} + +char *j_strcat(char *dest, char *txt) +{ + if(!txt) return(dest); + + while(*txt) + *dest++ = *txt++; + *dest = '\0'; + + return(dest); +} + +int j_strcmp(const char *a, const char *b) +{ + if(a == NULL || b == NULL) + return -1; + + while(*a == *b && *a != '\0' && *b != '\0'){ a++; b++; } + + if(*a == *b) return 0; + + return -1; +} + +int j_strcasecmp(const char *a, const char *b) +{ + if(a == NULL || b == NULL) + return -1; + else + return strcasecmp(a, b); +} + +int j_strncmp(const char *a, const char *b, int i) +{ + if(a == NULL || b == NULL) + return -1; + else + return strncmp(a, b, i); +} + +int j_strncasecmp(const char *a, const char *b, int i) +{ + if(a == NULL || b == NULL) + return -1; + else + return strncasecmp(a, b, i); +} + +int j_strlen(const char *a) +{ + if(a == NULL) + return 0; + else + return strlen(a); +} + +int j_atoi(const char *a, int def) +{ + if(a == NULL) + return def; + else + return atoi(a); +} + +char *j_attr(const char** atts, char *attr) +{ + int i = 0; + + while(atts[i] != '\0') + { + if(j_strcmp(atts[i],attr) == 0) return (char*)atts[i+1]; + i += 2; + } + + return NULL; +} + +/** like strchr, but only searches n chars */ +char *j_strnchr(const char *s, int c, int n) { + int count; + + for(count = 0; count < n; count++) + if(s[count] == (char) c) + return &((char *)s)[count]; + + return NULL; +} + +spool spool_new(pool p) +{ + spool s; + + s = pmalloc(p, sizeof(struct spool_struct)); + s->p = p; + s->len = 0; + s->last = NULL; + s->first = NULL; + return s; +} + +void _spool_add(spool s, char *goodstr) +{ + struct spool_node *sn; + + sn = pmalloc(s->p, sizeof(struct spool_node)); + sn->c = goodstr; + sn->next = NULL; + + s->len += strlen(goodstr); + if(s->last != NULL) + s->last->next = sn; + s->last = sn; + if(s->first == NULL) + s->first = sn; +} + +void spool_add(spool s, char *str) +{ + if(str == NULL || strlen(str) == 0) + return; + + _spool_add(s, pstrdup(s->p, str)); +} + +void spool_escape(spool s, char *raw, int len) +{ + if(raw == NULL || len <= 0) + return; + + _spool_add(s, strescape(s->p, raw, len)); +} + +void spooler(spool s, ...) +{ + va_list ap; + char *arg = NULL; + + if(s == NULL) + return; + + va_start(ap, s); + + /* loop till we hit our end flag, the first arg */ + while(1) + { + arg = va_arg(ap,char *); + if((spool)arg == s) + break; + else + spool_add(s, arg); + } + + va_end(ap); +} + +char *spool_print(spool s) +{ + char *ret,*tmp; + struct spool_node *next; + + if(s == NULL || s->len == 0 || s->first == NULL) + return NULL; + + ret = pmalloc(s->p, s->len + 1); + *ret = '\0'; + + next = s->first; + tmp = ret; + while(next != NULL) + { + tmp = j_strcat(tmp,next->c); + next = next->next; + } + + return ret; +} + +/** convenience :) */ +char *spools(pool p, ...) +{ + va_list ap; + spool s; + char *arg = NULL; + + if(p == NULL) + return NULL; + + s = spool_new(p); + + va_start(ap, p); + + /* loop till we hit our end flag, the first arg */ + while(1) + { + arg = va_arg(ap,char *); + if((pool)arg == p) + break; + else + spool_add(s, arg); + } + + va_end(ap); + + return spool_print(s); +} + + +char *strunescape(pool p, char *buf) +{ + int i,j=0; + char *temp; + + if (buf == NULL) return(NULL); + + if (strchr(buf,'&') == NULL) return(buf); + + if(p != NULL) + temp = pmalloc(p,strlen(buf)+1); + else + temp = malloc(strlen(buf)+1); + + if (temp == NULL) return(NULL); + + for(i=0;i': + newlen+=4; + break; + } + } + + if(p != NULL) + temp = pmalloc(p,newlen+1); + else + temp = malloc(newlen+1); + if(newlen == len) + { + memcpy(temp,buf,len); + temp[len] = '\0'; + return temp; + } + + for(i=j=0;i': + memcpy(&temp[j],">",4); + j += 4; + break; + default: + temp[j++] = buf[i]; + } + } + temp[j] = '\0'; + return temp; +} + +char *zonestr(char *file, int line) +{ + static char buff[64]; + int i; + + i = snprintf(buff,63,"%s:%d",file,line); + buff[i] = '\0'; + + return buff; +} + +/** convenience (originally by Thomas Muldowney) */ +void shahash_r(const char* str, char hashbuf[41]) { + unsigned char hashval[20]; + + sha1_hash((unsigned char *)str, strlen(str), hashval); + + hex_from_raw(hashval, 20, hashbuf); +} diff --git a/util/uri.h b/util/uri.h new file mode 100644 index 00000000..e2d62d06 --- /dev/null +++ b/util/uri.h @@ -0,0 +1,48 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/uri.h + * @brief common URIs + * @author Robert Norris + * $Revision: 1.1 $ + * $Date: 2004/04/30 00:53:54 $ + */ + +#ifndef INCL_UTIL_URI_H +#define INCL_UTIL_URI_H 1 + +/* known namespace uri */ +#define uri_STREAMS "http://etherx.jabber.org/streams" +#define uri_CLIENT "jabber:client" +#define uri_SERVER "jabber:server" +#define uri_DIALBACK "jabber:server:dialback" +#define uri_TLS "urn:ietf:params:xml:ns:xmpp-tls" +#define uri_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define uri_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define uri_XSESSION "urn:ietf:params:xml:ns:xmpp-session" +#define uri_STREAM_ERR "urn:ietf:params:xml:ns:xmpp-streams" +#define uri_STANZA_ERR "urn:ietf:params:xml:ns:xmpp-stanzas" +#define uri_COMPONENT "http://jabberd.jabberstudio.org/ns/component/1.0" +#define uri_SESSION "http://jabberd.jabberstudio.org/ns/session/1.0" +#define uri_RESOLVER "http://jabberd.jabberstudio.org/ns/resolver/1.0" +#define uri_XDATA "jabber:x:data" +#define uri_XML "http://www.w3.org/XML/1998/namespace" + +#endif diff --git a/util/util.h b/util/util.h new file mode 100644 index 00000000..93c8ff78 --- /dev/null +++ b/util/util.h @@ -0,0 +1,770 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ac-stdint.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_TYPES_H +# include +#endif + +#ifdef HAVE_NETINET_IN_H +# include +#endif + +#if defined(HAVE_SYS_TIME_H) +# include +#elif defined(HAVE_SYS_TIMEB_H) +# include +#endif +#ifdef HAVE_SYSLOG_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +#include + +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#ifdef HAVE_NETINET_IN_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif + +#include "subst/subst.h" + +#include "util/util_compat.h" + +#ifndef INCL_UTIL_H +#define INCL_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* expat is available */ +#define HAVE_EXPAT 1 + + +/* crypto hashing utils */ +#include "sha1.h" +#include "md5.h" + + +/* --------------------------------------------------------- */ +/* */ +/* Pool-based memory management routines */ +/* */ +/* --------------------------------------------------------- */ + +#ifdef POOL_DEBUG +/* prime number for top # of pools debugging */ +#define POOL_NUM 40009 +#endif + +/** pheap - singular allocation of memory */ +struct pheap +{ + void *block; + int size, used; +}; + +/** pool_cleaner - callback type which is associated + with a pool entry; invoked when the pool entry is + free'd */ +typedef void (*pool_cleaner)(void *arg); + +/** pfree - a linked list node which stores an + allocation chunk, plus a callback */ +struct pfree +{ + pool_cleaner f; + void *arg; + struct pheap *heap; + struct pfree *next; +}; + +/** pool - base node for a pool. Maintains a linked list + of pool entries (pfree) */ +typedef struct pool_struct +{ + int size; + struct pfree *cleanup; + struct pfree *cleanup_tail; + struct pheap *heap; +#ifdef POOL_DEBUG + char name[8], zone[32]; + int lsize; +} _pool, *pool; +#define pool_new() _pool_new(ZONE) +#define pool_heap(i) _pool_new_heap(i,ZONE) +#else +} _pool, *pool; +#define pool_heap(i) _pool_new_heap(i,NULL,0) +#define pool_new() _pool_new(NULL,0) +#endif + +pool _pool_new(char *zone, int line); /* new pool :) */ +pool _pool_new_heap(int size, char *zone, int line); /* creates a new memory pool with an initial heap size */ +void *pmalloc(pool p, int size); /* wrapper around malloc, takes from the pool, cleaned up automatically */ +void *pmalloc_x(pool p, int size, char c); /* Wrapper around pmalloc which prefils buffer with c */ +void *pmalloco(pool p, int size); /* YAPW for zeroing the block */ +char *pstrdup(pool p, const char *src); /* wrapper around strdup, gains mem from pool */ +void pool_stat(int full); /* print to stderr the changed pools and reset */ +char *pstrdupx(pool p, const char *src, int len); /* use given len */ +void pool_cleanup(pool p, pool_cleaner f, void *arg); /* calls f(arg) before the pool is freed during cleanup */ +void pool_free(pool p); /* calls the cleanup functions, frees all the data on the pool, and deletes the pool itself */ +int pool_size(pool p); /* returns total bytes allocated in this pool */ + + + + +/* --------------------------------------------------------- */ +/* */ +/* String management routines */ +/* */ +/** --------------------------------------------------------- */ +char *j_strdup(const char *str); /* provides NULL safe strdup wrapper */ +char *j_strcat(char *dest, char *txt); /* strcpy() clone */ +int j_strcmp(const char *a, const char *b); /* provides NULL safe strcmp wrapper */ +int j_strcasecmp(const char *a, const char *b); /* provides NULL safe strcasecmp wrapper */ +int j_strncmp(const char *a, const char *b, int i); /* provides NULL safe strncmp wrapper */ +int j_strncasecmp(const char *a, const char *b, int i); /* provides NULL safe strncasecmp wrapper */ +int j_strlen(const char *a); /* provides NULL safe strlen wrapper */ +int j_atoi(const char *a, int def); /* checks for NULL and uses default instead, convienence */ +char *j_attr(const char** atts, char *attr); /* decode attr's (from expat) */ +char *j_strnchr(const char *s, int c, int n); /* like strchr, but only searches n chars */ + +/** old convenience function, now in str.c */ +void shahash_r(const char* str, char hashbuf[41]); + +/* --------------------------------------------------------- */ +/* */ +/* Hashtable functions */ +/* */ +/* --------------------------------------------------------- */ +typedef struct xhn_struct +{ + struct xhn_struct *next; + const char *key; + void *val; +} *xhn, _xhn; + +typedef struct xht_struct +{ + pool p; + int prime; + int dirty; + int count; + struct xhn_struct *zen; + int iter_bucket; + xhn iter_node; +} *xht, _xht; + +xht xhash_new(int prime); +void xhash_put(xht h, const char *key, void *val); +void xhash_putx(xht h, const char *key, int len, void *val); +void *xhash_get(xht h, const char *key); +void *xhash_getx(xht h, const char *key, int len); +void xhash_zap(xht h, const char *key); +void xhash_zapx(xht h, const char *key, int len); +void xhash_free(xht h); +typedef void (*xhash_walker)(xht h, const char *key, void *val, void *arg); +void xhash_walk(xht h, xhash_walker w, void *arg); +int xhash_dirty(xht h); +int xhash_count(xht h); +pool xhash_pool(xht h); + +/* iteration functions */ +int xhash_iter_first(xht h); +int xhash_iter_next(xht h); +void xhash_iter_zap(xht h); +int xhash_iter_get(xht h, const char **key, void **val); + +/* --------------------------------------------------------- */ +/* */ +/* XML escaping utils */ +/* */ +/* --------------------------------------------------------- */ +char *strescape(pool p, char *buf, int len); /* Escape <>&'" chars */ +char *strunescape(pool p, char *buf); + + +/* --------------------------------------------------------- */ +/* */ +/* String pools (spool) functions */ +/* */ +/* --------------------------------------------------------- */ +struct spool_node +{ + char *c; + struct spool_node *next; +}; + +typedef struct spool_struct +{ + pool p; + int len; + struct spool_node *last; + struct spool_node *first; +} *spool; + +spool spool_new(pool p); /* create a string pool */ +void spooler(spool s, ...); /* append all the char * args to the pool, terminate args with s again */ +char *spool_print(spool s); /* return a big string */ +void spool_add(spool s, char *str); /* add a single string to the pool */ +void spool_escape(spool s, char *raw, int len); /* add and xml escape a single string to the pool */ +char *spools(pool p, ...); /* wrap all the spooler stuff in one function, the happy fun ball! */ + + +/* known namespace uri */ +#define uri_STREAMS "http://etherx.jabber.org/streams" +#define uri_CLIENT "jabber:client" +#define uri_SERVER "jabber:server" +#define uri_DIALBACK "jabber:server:dialback" +#define uri_TLS "urn:ietf:params:xml:ns:xmpp-tls" +#define uri_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define uri_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define uri_XSESSION "urn:ietf:params:xml:ns:xmpp-session" +#define uri_STREAM_ERR "urn:ietf:params:xml:ns:xmpp-streams" +#define uri_STANZA_ERR "urn:ietf:params:xml:ns:xmpp-stanzas" +#define uri_COMPONENT "http://jabberd.jabberstudio.org/ns/component/1.0" +#define uri_SESSION "http://jabberd.jabberstudio.org/ns/session/1.0" +#define uri_RESOLVER "http://jabberd.jabberstudio.org/ns/resolver/1.0" +#define uri_XDATA "jabber:x:data" +#define uri_XML "http://www.w3.org/XML/1998/namespace" + +#define uri_DIALBACK_L 22 /* strlen(uri_DIALBACK) */ + +/* + * JID manipulation. Validity is checked via stringprep, using the "nodeprep", + * "nameprep" and "resourceprep" profiles (see xmpp-core section 3). + * + * The provided functions are mainly for convenience. The application should + * fill out node, domain and resource directly. When they modify these, they + * should either call jid_expand(), or set the dirty flag. + */ + +/** preparation cache, for speed */ +typedef struct prep_cache_st { + xht node; + xht domain; + xht resource; +} *prep_cache_t; + +prep_cache_t prep_cache_new(void); +void prep_cache_free(prep_cache_t pc); +char *prep_cache_node_get(prep_cache_t pc, char *from); +void prep_cache_node_set(prep_cache_t pc, char *from, char *to); +char *prep_cache_domain_get(prep_cache_t pc, char *from); +void prep_cache_domain_set(prep_cache_t pc, char *from, char *to); +char *prep_cache_resource_get(prep_cache_t pc, char *from); +void prep_cache_resource_set(prep_cache_t pc, char *from, char *to); + +/** these sizings come from xmpp-core */ +#define MAXLEN_JID_COMP 1023 /* XMPP (RFC3920) 3.1 */ +#define MAXLEN_JID 3071 /* nodename (1023) + '@' + domain (1023) + '/' + resource (1023) = 3071 */ + +typedef struct jid_st { + /* cache for prep, if any */ + prep_cache_t pc; + + /* basic components of the jid */ + unsigned char *node; + unsigned char *domain; + unsigned char *resource; + + /* Points to jid broken with \0s into componets. node/domain/resource point + * into this string (or to statically allocated empty string, if they are + * empty) */ + unsigned char *jid_data; + /* Valid only when jid_data != NULL. When = 0, jid_data is statically + * allocated. Otherwise it tells length of the allocated data. Used to + * implement jid_dup() */ + size_t jid_data_len; + + /* the "user" part of the jid (sans resource) */ + unsigned char *_user; + + /* the complete jid */ + unsigned char *_full; + + /* application should set to 1 if user/full need regenerating */ + int dirty; + + /* for lists of jids */ + struct jid_st *next; +} *jid_t; + +typedef enum { + jid_NODE = 1, + jid_DOMAIN = 2, + jid_RESOURCE = 3 +} jid_part_t; + +/** JID static buffer **/ +typedef char jid_static_buf[3*1025]; + +/** make a new jid, and call jid_reset() to populate it */ +jid_t jid_new(prep_cache_t pc, const unsigned char *id, int len); + +/** Make jid to use static buffer (jid data won't be allocated dynamically, but + * given buffer will be always used. */ +void jid_static(jid_t jid, jid_static_buf *buf); + +/** clear and populate the jid with the given id. if id == NULL, just clears the jid to 0 */ +jid_t jid_reset(jid_t jid, const unsigned char *id, int len); +jid_t jid_reset_components(jid_t jid, const unsigned char *node, const unsigned char *domain, const unsigned char *resource); + +/** free the jid */ +void jid_free(jid_t jid); + +/** do string preparation on a jid */ +int jid_prep(jid_t jid); + +/** fill jid's resource with a random string **/ +void jid_random_part(jid_t jid, jid_part_t part); + +/** expands user and full if the dirty flag is set */ +void jid_expand(jid_t jid); + +/** return the user or full jid. these call jid_expand to make sure the user and + * full jid are up to date */ +const unsigned char *jid_user(jid_t jid); +const unsigned char *jid_full(jid_t jid); + +/** compare two user or full jids. these call jid_expand, then strcmp. returns + * 0 if they're the same, < 0 if a < b, > 0 if a > b */ +int jid_compare_user(jid_t a, jid_t b); +int jid_compare_full(jid_t a, jid_t b); + +/** duplicate a jid */ +jid_t jid_dup(jid_t jid); + +/** list helpers */ + +/** see if a jid is present in a list */ +int jid_search(jid_t list, jid_t jid); + +/** remove a jid from a list, and return the new list */ +jid_t jid_zap(jid_t list, jid_t jid); + +/** insert of a copy of jid into list, avoiding dups */ +jid_t jid_append(jid_t list, jid_t jid); + + +/* logging */ + +typedef enum { + log_STDOUT, + log_SYSLOG, + log_FILE +} log_type_t; + +typedef struct log_st +{ + log_type_t type; + FILE *file; +} *log_t; + +typedef struct log_facility_st +{ + char *facility; + int number; +} log_facility_t; + +extern log_t log_new(log_type_t type, char *ident, char *facility); +extern void log_write(log_t log, int level, const char *msgfmt, ...); +extern void log_free(log_t log); + + +/* Not A DOM */ + +/* using nad: + * + * nad is very simplistic, and requires all string handling to use a length. + * Apps using this must be aware of the structure and access it directly for + * most information. nads can only be built by successively using the _append_ + * functions correctly. After built, they can be modified using other functions, + * or by direct access. To access cdata on an elem or attr, use nad->cdata + + * nad->xxx[index].ixxx for the start, and .lxxx for len. + * + * Namespace support seems to work, but hasn't been thoroughly tested. in + * particular, editing the nad after its creation might have quirks. use at + * your own risk! Note that nad_add_namespace() brings a namespace into scope + * for the next element added with nad_append_elem(), nad_insert_elem() or + * nad_wrap_elem() (and by extension, any of its subelements). This is the same + * way that Expat does things, so nad_add_namespace() can be driven from the + * Expat's StartNamespaceDeclHandler. + */ + +typedef struct nad_st **nad_cache_t; + +struct nad_elem_st +{ + int parent; + int iname, lname; + int icdata, lcdata; /* cdata within this elem (up to first child) */ + int itail, ltail; /* cdata after this elem */ + int attr; + int ns; + int my_ns; + int depth; +}; + +struct nad_attr_st +{ + int iname, lname; + int ival, lval; + int my_ns; + int next; +}; + +struct nad_ns_st +{ + int iuri, luri; + int iprefix, lprefix; + int next; +}; + +typedef struct nad_st +{ + nad_cache_t cache; /* he who gave us life */ + struct nad_elem_st *elems; + struct nad_attr_st *attrs; + struct nad_ns_st *nss; + char *cdata; + int *depths; /* for tracking the last elem at a depth */ + int elen, alen, nlen, clen, dlen; + int ecur, acur, ncur, ccur; + int scope; /* currently scoped namespaces, get attached to the next element */ + struct nad_st *next; /* for keeping a list of nads */ +} *nad_t; + +/** create a new cache for nads */ +nad_cache_t nad_cache_new(void); + +/** free the cache */ +void nad_cache_free(nad_cache_t cache); + +/** create a new nad */ +nad_t nad_new(nad_cache_t cache); + +/** copy a nad */ +nad_t nad_copy(nad_t nad); + +/** free that nad */ +void nad_free(nad_t nad); + +/** find the next element with this name/depth */ +/** 0 for siblings, 1 for children and so on */ +int nad_find_elem(nad_t nad, int elem, int ns, const char *name, int depth); + +/** find the first matching attribute (and optionally value) */ +int nad_find_attr(nad_t nad, int elem, int ns, const char *name, const char *val); + +/** find the first matching namespace (and optionally prefix) */ +int nad_find_namespace(nad_t nad, int elem, const char *uri, const char *prefix); + +/** find a namespace in scope (and optionally prefix) */ +int nad_find_scoped_namespace(nad_t nad, const char *uri, const char *prefix); + +/** reset or store the given attribute */ +void nad_set_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen); + +/** insert and return a new element as a child of this one */ +int nad_insert_elem(nad_t nad, int elem, int ns, const char *name, const char *cdata); + +/** wrap an element with another element */ +void nad_wrap_elem(nad_t nad, int elem, int ns, const char *name); + +/** append and return a new element */ +int nad_append_elem(nad_t nad, int ns, const char *name, int depth); + +/** append attribs to the last element */ +int nad_append_attr(nad_t nad, int ns, const char *name, const char *val); + +/** append more cdata to the last element */ +void nad_append_cdata(nad_t nad, const char *cdata, int len, int depth); + +/** add a namespace to the next element (ie, called when the namespace comes into scope) */ +int nad_add_namespace(nad_t nad, const char *uri, const char *prefix); + +/** declare a namespace on an already existing element */ +int nad_append_namespace(nad_t nad, int elem, const char *uri, const char *prefix); + +/** create a string representation of the given element (and children), point references to it */ +void nad_print(nad_t nad, int elem, char **xml, int *len); + +/** serialize and deserialize a nad */ +void nad_serialize(nad_t nad, char **buf, int *len); +nad_t nad_deserialize(nad_cache_t cache, const char *buf); + +#ifdef HAVE_EXPAT +/** create a nad from raw xml */ +nad_t nad_parse(nad_cache_t cache, const char *buf, int len); +#endif + +/* these are some helpful macros */ +#define NAD_ENAME(N,E) (N->cdata + N->elems[E].iname) +#define NAD_ENAME_L(N,E) (N->elems[E].lname) +#define NAD_CDATA(N,E) (N->cdata + N->elems[E].icdata) +#define NAD_CDATA_L(N,E) (N->elems[E].lcdata) +#define NAD_ANAME(N,A) (N->cdata + N->attrs[A].iname) +#define NAD_ANAME_L(N,A) (N->attrs[A].lname) +#define NAD_AVAL(N,A) (N->cdata + N->attrs[A].ival) +#define NAD_AVAL_L(N,A) (N->attrs[A].lval) +#define NAD_NURI(N,NS) (N->cdata + N->nss[NS].iuri) +#define NAD_NURI_L(N,NS) (N->nss[NS].luri) +#define NAD_NPREFIX(N,NS) (N->cdata + N->nss[NS].iprefix) +#define NAD_NPREFIX_L(N,NS) (N->nss[NS].lprefix) + +#define NAD_ENS(N,E) (N->elems[E].my_ns) +#define NAD_ANS(N,A) (N->attrs[A].my_ns) + + +/* config files */ +typedef struct config_elem_st *config_elem_t; +typedef struct config_st *config_t; + +/** holder for the config hash and nad */ +struct config_st +{ + xht hash; + nad_cache_t nads; + nad_t nad; +}; + +/** a single element */ +struct config_elem_st +{ + char **values; + int nvalues; + char ***attrs; +}; + +extern config_t config_new(void); +extern int config_load(config_t c, char *file); +extern config_elem_t config_get(config_t c, char *key); +extern char *config_get_one(config_t c, char *key, int num); +extern int config_count(config_t c, char *key); +extern char *config_get_attr(config_t c, char *key, int num, char *attr); +extern void config_free(config_t); + + +/* + * IP-based access controls + */ + +typedef struct access_rule_st +{ + struct sockaddr_storage ip; + int mask; +} *access_rule_t; + +typedef struct access_st +{ + int order; /* 0 = allow,deny 1 = deny,allow */ + + access_rule_t allow; + int nallow; + + access_rule_t deny; + int ndeny; +} *access_t; + +access_t access_new(int order); +void access_free(access_t access); +int access_allow(access_t access, char *ip, char *mask); +int access_deny(access_t access, char *ip, char *mask); +int access_check(access_t access, char *ip); + + +/* + * rate limiting + */ + +typedef struct rate_st +{ + int total; /* if we exceed this many events */ + int seconds; /* in this many seconds */ + int wait; /* then go bad for this many seconds */ + + time_t time; /* time we started counting events */ + int count; /* event count */ + + time_t bad; /* time we went bad, or 0 if we're not */ +} *rate_t; + +rate_t rate_new(int total, int seconds, int wait); +void rate_free(rate_t rt); +void rate_reset(rate_t rt); +void rate_add(rate_t rt, int count); +int rate_left(rate_t rt); +int rate_check(rate_t rt); /* 1 == good, 0 == bad */ + +/* + * helpers for ip addresses + */ + +#include "inaddr.h" /* used in mio as well */ + +/* + * serialisation helper functions + */ + +int ser_string_get(char **dest, int *source, const char *buf, int len); +int ser_int_get(int *dest, int *source, const char *buf, int len); +void ser_string_set(char *source, int *dest, char **buf, int *len); +void ser_int_set(int source, int *dest, char **buf, int *len); + +/* + * priority queues + */ + +typedef struct _jqueue_node_st *_jqueue_node_t; +struct _jqueue_node_st { + void *data; + + int priority; + + _jqueue_node_t next; + _jqueue_node_t prev; +}; + +typedef struct _jqueue_st { + pool p; + _jqueue_node_t cache; + + _jqueue_node_t front; + _jqueue_node_t back; + + int size; +} *jqueue_t; + +jqueue_t jqueue_new(void); +void jqueue_free(jqueue_t q); +void jqueue_push(jqueue_t q, void *data, int pri); +void *jqueue_pull(jqueue_t q); +int jqueue_size(jqueue_t q); + + +/* ISO 8601 / JEP-0082 date/time manipulation */ +typedef enum { + dt_DATE = 1, + dt_TIME = 2, + dt_DATETIME = 3, + dt_LEGACY = 4 +} datetime_t; + +time_t datetime_in(char *date); +void datetime_out(time_t t, datetime_t type, char *date, int datelen); + + +/* base64 functions */ +extern int ap_base64decode_len(const char *bufcoded, int buflen); +extern int ap_base64decode(char *bufplain, const char *bufcoded, int buflen); +extern int ap_base64decode_binary(unsigned char *bufplain, const char *bufcoded, int buflen); +extern int ap_base64encode_len(int len); +extern int ap_base64encode(char *encoded, const char *string, int len); +extern int ap_base64encode_binary(char *encoded, const unsigned char *string, int len); + +/* convenience, result string must be free()'d by caller */ +extern char *b64_encode(char *buf, int len); +extern char *b64_decode(char *buf); + + +/* stanza manipulation */ +#define stanza_err_BAD_REQUEST (100) +#define stanza_err_CONFLICT (101) +#define stanza_err_FEATURE_NOT_IMPLEMENTED (102) +#define stanza_err_FORBIDDEN (103) +#define stanza_err_GONE (104) +#define stanza_err_INTERNAL_SERVER_ERROR (105) +#define stanza_err_ITEM_NOT_FOUND (106) +#define stanza_err_JID_MALFORMED (107) +#define stanza_err_NOT_ACCEPTABLE (108) +#define stanza_err_NOT_ALLOWED (109) +#define stanza_err_PAYMENT_REQUIRED (110) +#define stanza_err_RECIPIENT_UNAVAILABLE (111) +#define stanza_err_REDIRECT (112) +#define stanza_err_REGISTRATION_REQUIRED (113) +#define stanza_err_REMOTE_SERVER_NOT_FOUND (114) +#define stanza_err_REMOTE_SERVER_TIMEOUT (115) +#define stanza_err_RESOURCE_CONSTRAINT (116) +#define stanza_err_SERVICE_UNAVAILABLE (117) +#define stanza_err_SUBSCRIPTION_REQUIRED (118) +#define stanza_err_UNDEFINED_CONDITION (119) +#define stanza_err_UNEXPECTED_REQUEST (120) +#define stanza_err_OLD_UNAUTH (121) +#define stanza_err_LAST (122) + +extern nad_t stanza_error(nad_t nad, int elem, int err); +extern nad_t stanza_tofrom(nad_t nad, int elem); + + +/* hex conversion utils */ +void hex_from_raw(char *in, int inlen, char *out); +int hex_to_raw(char *in, int inlen, char *out); + + +/* xdata in a seperate file */ +#include "xdata.h" + + +/* debug logging */ +int get_debug_flag(void); +void set_debug_flag(int v); +void debug_log(char *file, int line, const char *msgfmt, ...); +#define ZONE __FILE__,__LINE__ +#define MAX_DEBUG 8192 + +/* if no debug, basically compile it out */ +#ifdef DEBUG +#define log_debug if(get_debug_flag()) debug_log +#else +#define log_debug if(0) debug_log +#endif + +/* Portable signal function */ +typedef void jsighandler_t(int); +jsighandler_t* jabber_signal(int signo, jsighandler_t *func); + +#ifdef __cplusplus +} +#endif + +#endif /* INCL_UTIL_H */ + + diff --git a/util/util_compat.h b/util/util_compat.h new file mode 100644 index 00000000..a560e589 --- /dev/null +++ b/util/util_compat.h @@ -0,0 +1,107 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifndef INCL_UTIL_COMPAT_H +#define INCL_UTIL_COMPAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file util/util_compat.h + * @brief define the structures that could be missing in old libc implementations + */ + +#ifndef PF_INET6 +# define PF_INET6 10 /**< protcol family for IPv6 */ +#endif + +#ifndef AF_INET6 +# define AF_INET6 PF_INET6 /**< address family for IPv6 */ +#endif + +#ifndef INET6_ADDRSTRLEN +# define INET6_ADDRSTRLEN 46 /**< maximum length of the string + representation of an IPv6 address */ +#endif + + +#ifndef IN6_IS_ADDR_V4MAPPED + /** check if an IPv6 is just a mapped IPv4 address */ +#define IN6_IS_ADDR_V4MAPPED(a) \ + ((*(const uint32_t *)(const void *)(&(a)->s6_addr[0]) == 0) && \ + (*(const uint32_t *)(const void *)(&(a)->s6_addr[4]) == 0) && \ + (*(const uint32_t *)(const void *)(&(a)->s6_addr[8]) == ntohl(0x0000ffff))) +#endif + +#ifndef HAVE_SA_FAMILY_T +typedef unsigned short sa_family_t; +#endif + +#ifndef HAVE_STRUCT_IN6_ADDR +/** + * structure that contains a plain IPv6 address (only defined if + * not contained in the libc + */ +struct in6_addr { + uint8_t s6_addr[16]; /**< IPv6 address */ +}; +#endif /* NO_IN6_ADDR */ + +#ifndef HAVE_STRUCT_SOCKADDR_IN6 +/** + * structure that contains an IPv6 including some additional attributes + * (only defined if not contained in the libc) + */ +struct sockaddr_in6 { +#ifdef SIN6_LEN + uint8_t sin6_len; /**< length of this struct */ +#endif /* SIN6_LEN */ + sa_family_t sin6_family; /**< address family (AF_INET6) */ + in_port_t sin6_port; /**< transport layer port # */ + uint32_t sin6_flowinfo; /**< IPv6 traffic class and flow info */ + struct in6_addr sin6_addr; /**< IPv6 address */ + uint32_t sin6_scope_id; /**< set of interfaces for a scope */ +}; +#endif /* NO_SOCKADDR_IN6 */ + +#ifndef HAVE_STRUCT_SOCKADDR_STORAGE +/** + * container for sockaddr_in and sockaddr_in6 structures, handled like + * an object in jabberd2 code + * (this definition is not fully compatible with RFC 2553, + * but it is enough for us) */ +#define _SS_PADSIZE (128-sizeof(sa_family_t)) +struct sockaddr_storage { + sa_family_t ss_family; /**< address family */ + char __ss_pad[_SS_PADSIZE]; /**< padding to a size of 128 bytes */ +}; +#endif /* NO_SOCKADDR_STORAGE */ + +#ifdef __cplusplus +} +#endif + +#endif /* INCL_UTIL_H */ diff --git a/util/xconfig.c b/util/xconfig.c new file mode 100644 index 00000000..e4a3a3b6 --- /dev/null +++ b/util/xconfig.c @@ -0,0 +1,274 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "xconfig.h" + +#include "expat/expat.h" + +#include +#include +#include +#include + +/** new xconfig structure */ +xconfig_t xconfig_new(pool_t p) { + xconfig_t c; + + c = (xconfig_t) pmalloco(p, sizeof(struct xconfig_st)); + c->p = p; + + c->hash = xhash_new(p, 501); + + return c; +} + +struct build_data { + nad_t nad; + int depth; +}; + +static void _xconfig_startElement(void *arg, const char *name, const char **atts) { + struct build_data *bd = (struct build_data *) arg; + int i = 0; + + nad_append_elem(bd->nad, -1, (char *) name, bd->depth); + while(atts[i] != NULL) + { + nad_append_attr(bd->nad, -1, (char *) atts[i], (char *) atts[i + 1]); + i += 2; + } + + bd->depth++; +} + +static void _xconfig_endElement(void *arg, const char *name) { + struct build_data *bd = (struct build_data *) arg; + + bd->depth--; +} + +static void _xconfig_charData(void *arg, const char *str, int len) { + struct build_data *bd = (struct build_data *) arg; + + nad_append_cdata(bd->nad, (char *) str, len, bd->depth); +} + +static void _xconfig_cleanup(void *arg) { + xconfig_t c = (xconfig_t) arg; + xconfig_elem_t elem; + + if(xhash_iter_first(c->hash)) + do { + xhash_iter_get(c->hash, NULL, (void *) &elem); + free(elem->values); + free(elem->attrs); + } while(xhash_iter_next(c->hash)); +} + +/** turn an xml file into a xconfig hash */ +int xconfig_load(xconfig_t c, char *file) { + struct build_data bd; + FILE *f; + XML_Parser p; + int done, len, end, i, j, attr; + char buf[1024], *next; + struct nad_elem_st **path; + xconfig_elem_t elem; + + assert((int) c); + assert((int) file); + + /* only run once */ + assert((int) !c->nad); + + /* open the file */ + f = fopen(file, "r"); + if(f == NULL) { + fprintf(stderr, "xconfig_load: couldn't open %s for reading: %s\n", file, strerror(errno)); + return 1; + } + + /* new parser */ + p = XML_ParserCreate(NULL); + if(p == NULL) { + fprintf(stderr, "xconfig_load: couldn't allocate XML parser\n"); + fclose(f); + return 1; + } + + /* nice new nad to parse it into */ + bd.nad = nad_new(c->p); + bd.depth = 0; + + /* setup the parser */ + XML_SetUserData(p, (void *) &bd); + XML_SetElementHandler(p, _xconfig_startElement, _xconfig_endElement); + XML_SetCharacterDataHandler(p, _xconfig_charData); + + for(;;) { + /* read that file */ + len = fread(buf, 1, 1024, f); + if(ferror(f)) { + fprintf(stderr, "xconfig_load: read error: %s\n", strerror(errno)); + XML_ParserFree(p); + fclose(f); + return 1; + } + done = feof(f); + + /* parse it */ + if(!XML_Parse(p, buf, len, done)) { + fprintf(stderr, "xconfig_load: parse error at line %d: %s\n", XML_GetCurrentLineNumber(p), XML_ErrorString(XML_GetErrorCode(p))); + XML_ParserFree(p); + fclose(f); + return 1; + } + + if(done) + break; + } + + /* done reading */ + XML_ParserFree(p); + fclose(f); + + /* now, turn the nad into a xconfig hash */ + path = NULL; + len = 0, end = 0; + /* start at 1, so we skip the root element */ + for(i = 1; i < bd.nad->ecur; i++) { + /* make sure we have enough room to add this element to our path */ + if(end <= bd.nad->elems[i].depth) { + end = bd.nad->elems[i].depth + 1; + path = (struct nad_elem_st **) realloc((void *) path, sizeof(struct nad_elem_st *) * end); + } + + /* save this path element */ + path[bd.nad->elems[i].depth] = &bd.nad->elems[i]; + len = bd.nad->elems[i].depth + 1; + + /* construct the key from the current path */ + next = buf; + for(j = 1; j < len; j++) { + strncpy(next, bd.nad->cdata + path[j]->iname, path[j]->lname); + next = next + path[j]->lname; + *next = '.'; + next++; + } + next--; + *next = '\0'; + + /* find the xconfig element for this key */ + elem = xhash_get(c->hash, buf); + if(elem == NULL) { + /* haven't seen it before, so create it */ + elem = pmalloco(c->p, sizeof(struct xconfig_elem_st)); + xhash_put(c->hash, pstrdup(c->p, buf), elem); + } + + /* make room for this value .. can't realloc off a pool, so + * we do it this way and let _xconfig_cleanup sort it out */ + elem->values = realloc((void *) elem->values, sizeof(char *) * (elem->nvalues + 1)); + + /* and copy it in */ + if(NAD_CDATA_L(bd.nad, i) > 0) + elem->values[elem->nvalues] = pstrdupx(c->p, NAD_CDATA(bd.nad, i), NAD_CDATA_L(bd.nad, i)); + else + elem->values[elem->nvalues] = "1"; + + /* make room for the attribute lists */ + elem->attrs = realloc((void *) elem->attrs, sizeof(char **) * (elem->nvalues + 1)); + elem->attrs[elem->nvalues] = NULL; + + /* count the attributes */ + for(attr = bd.nad->elems[i].attr, j = 0; attr >= 0; attr = bd.nad->attrs[attr].next, j++); + + /* make space */ + elem->attrs[elem->nvalues] = pmalloc(c->p, sizeof(char *) * (j * 2 + 2)); + + /* if we have some */ + if(j > 0) { + /* copy them in */ + j = 0; + attr = bd.nad->elems[i].attr; + while(attr >= 0) { + elem->attrs[elem->nvalues][j] = pstrdupx(c->p, NAD_ANAME(bd.nad, attr), NAD_ANAME_L(bd.nad, attr)); + elem->attrs[elem->nvalues][j + 1] = pstrdupx(c->p, NAD_AVAL(bd.nad, attr), NAD_AVAL_L(bd.nad, attr)); + + j += 2; + attr = bd.nad->attrs[attr].next; + } + } + + /* do this and we can use j_attr */ + elem->attrs[elem->nvalues][j] = NULL; + elem->attrs[elem->nvalues][j + 1] = NULL; + + elem->nvalues++; + } + + /* get the stuff cleaned up at the end */ + pool_cleanup(c->p, _xconfig_cleanup, (void *) c); + + if(path != NULL) + free(path); + + c->nad = bd.nad; + + return 0; +} + +/** get the xconfig element for this key */ +xconfig_elem_t xconfig_get(xconfig_t c, char *key) { + return xhash_get(c->hash, key); +} + +/** get xconfig value n for this key */ +char *xconfig_get_one(xconfig_t c, char *key, int num) { + xconfig_elem_t elem = xhash_get(c->hash, key); + + if(elem == NULL) + return NULL; + + if(num >= elem->nvalues) + return NULL; + + return elem->values[num]; +} + +/** how many values for this key? */ +int xconfig_count(xconfig_t c, char *key) { + xconfig_elem_t elem = xhash_get(c->hash, key); + + if(elem == NULL) + return 0; + + return elem->nvalues; +} + +/** get an attr for this value */ +char *xconfig_get_attr(xconfig_t c, char *key, int num, char *attr) { + xconfig_elem_t elem = xhash_get(c->hash, key); + + if(num >= elem->nvalues || elem->attrs == NULL || elem->attrs[num] == NULL) + return NULL; + + return j_attr((const char **) elem->attrs[num], attr); +} diff --git a/util/xconfig.h b/util/xconfig.h new file mode 100644 index 00000000..d1e99d99 --- /dev/null +++ b/util/xconfig.h @@ -0,0 +1,59 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/xconfig.h + * @brief XML config files + * @author Robert Norris + * $Revision: 1.2 $ + * $Date: 2004/05/05 23:49:38 $ + */ + +#ifndef INCL_UTIL_XCONFIG_H +#define INCL_UTIL_XCONFIG_H 1 + +#include "xhash.h" +#include "pool.h" +#include "nad.h" + +typedef struct xconfig_st *xconfig_t; +typedef struct xconfig_elem_st *xconfig_elem_t; + +/** holder for the xconfig hash and nad */ +struct xconfig_st { + pool_t p; + xhash_t hash; + nad_t nad; +}; + +/** a single element */ +struct xconfig_elem_st { + char **values; + int nvalues; + char ***attrs; +}; + +extern xconfig_t xconfig_new(pool_t p); +extern int xconfig_load(xconfig_t c, char *file); +extern xconfig_elem_t xconfig_get(xconfig_t c, char *key); +extern char *xconfig_get_one(xconfig_t c, char *key, int num); +extern int xconfig_count(xconfig_t c, char *key); +extern char *xconfig_get_attr(xconfig_t c, char *key, int num, char *attr); + +#endif diff --git a/util/xdata.c b/util/xdata.c new file mode 100644 index 00000000..57ef136e --- /dev/null +++ b/util/xdata.c @@ -0,0 +1,389 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* xdata, whee! */ + +#include "util.h" + +/** creation */ +xdata_t xdata_new(xdata_type_t type, char *title, char *instructions) { + pool p; + xdata_t xd; + + assert((int) type); + + p = pool_new(); + + xd = pmalloco(p, sizeof(struct _xdata_st)); + + xd->p = p; + + xd->type = type; + + if(title != NULL) xd->title = pstrdup(xd->p, title); + if(instructions != NULL) xd->instructions = pstrdup(xd->p, instructions); + + log_debug(ZONE, "created new xd; title=%s, instructions=%s", title, instructions); + + return xd; +} + +/** new field */ +xdata_field_t xdata_field_new(xdata_t xd, xdata_field_type_t type, char *var, char *label, char *desc, int required) { + xdata_field_t xdf; + + assert((int) xd); + assert((int) type); + assert((int) var); + + xdf = pmalloco(xd->p, sizeof(struct _xdata_field_st)); + + xdf->p = xd->p; + + xdf->type = type; + + xdf->var = pstrdup(xdf->p, var); + + if(label != NULL) xdf->label = pstrdup(xdf->p, label); + if(desc != NULL) xdf->desc = pstrdup(xdf->p, desc); + + xdf->required = required; + + return xdf; +} + +/** new item */ +xdata_item_t xdata_item_new(xdata_t xd) { + xdata_item_t xdi; + + assert((int) xd); + + xdi = pmalloco(xd->p, sizeof(struct _xdata_item_st)); + + xdi->p = xd->p; + + return xdi; +} + +/** field insertion */ +void xdata_add_field(xdata_t xd, xdata_field_t xdf) { + assert((int) xd); + assert((int) xdf); + + if(xd->fields == NULL) + xd->fields = xd->flast = xdf; + else { + xd->flast->next = xdf; + xd->flast = xdf; + } +} + +void xdata_add_rfield(xdata_t xd, xdata_field_t xdf) { + assert((int) xd); + assert((int) xdf); + + if(xd->rfields == NULL) + xd->rfields = xd->rflast = xdf; + else { + xd->rflast->next = xdf; + xd->rflast = xdf; + } +} + +void xdata_add_field_item(xdata_item_t xdi, xdata_field_t xdf) { + assert((int) xdi); + assert((int) xdf); + + if(xdi->fields == NULL) + xdi->fields = xdi->flast = xdf; + else { + xdi->flast->next = xdf; + xdi->flast = xdf; + } +} + +/** item insertion */ +void xdata_add_item(xdata_t xd, xdata_item_t xdi) { + assert((int) xd); + assert((int) xdi); + + if(xd->items == NULL) + xd->items = xd->ilast = xdi; + else { + xd->ilast->next = xdi; + xd->ilast = xdi; + } +} + +/** option insertion */ +void xdata_option_new(xdata_field_t xdf, char *value, int lvalue, char *label, int llabel) { + xdata_option_t xdo; + + assert((int) xdf); + assert((int) value); + + xdo = pmalloco(xdf->p, sizeof(struct _xdata_option_st)); + + xdo->p = xdf->p; + + if(lvalue <= 0) lvalue = strlen(value); + xdo->value = pstrdupx(xdo->p, value, lvalue); + + if(label != NULL) { + if(llabel <= 0) llabel = strlen(label); + xdo->label = pstrdupx(xdo->p, label, llabel); + } + + xdf->olast->next = xdo; + xdf->olast = xdo; + if(xdf->options == NULL) xdf->options = xdo; +} + +/** value insertion */ +void xdata_add_value(xdata_field_t xdf, char *value, int vlen) { + int first = 0; + + assert((int) xdf); + assert((int) value); + + if(vlen <= 0) vlen = strlen(value); + + if(xdf->values == NULL) + first = 1; + + xdf->values = (char **) realloc(xdf->values, sizeof(char *) * (xdf->nvalues + 1)); + xdf->values[xdf->nvalues] = pstrdupx(xdf->p, value, vlen); + xdf->nvalues++; + + if(first) + pool_cleanup(xdf->p, free, xdf->values); +} + +/** rip out a field */ +static xdata_field_t _xdata_field_parse(xdata_t xd, nad_t nad, int root) { + xdata_field_t xdf; + int attr, elem, eval; + + xdf = pmalloco(xd->p, sizeof(struct _xdata_field_st)); + + xdf->p = xd->p; + + attr = nad_find_attr(nad, root, -1, "var", NULL); + if(attr >= 0) + xdf->var = pstrdupx(xdf->p, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + attr = nad_find_attr(nad, root, -1, "label", NULL); + if(attr >= 0) + xdf->label = pstrdupx(xdf->p, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + attr = nad_find_attr(nad, root, -1, "desc", NULL); + if(attr >= 0) + xdf->desc = pstrdupx(xdf->p, NAD_AVAL(nad, attr), NAD_AVAL_L(nad, attr)); + + if(nad_find_elem(nad, root, NAD_ENS(nad, root), "required", 1) >= 0) + xdf->required = 1; + + attr = nad_find_attr(nad, root, -1, "type", NULL); + if(attr >= 0) { + if(NAD_AVAL_L(nad, attr) == 7 && strncmp("boolean", NAD_AVAL(nad, attr), 7) == 0) + xdf->type = xd_field_BOOLEAN; + else if(NAD_AVAL_L(nad, attr) == 5 && strncmp("fixed", NAD_AVAL(nad, attr), 5) == 0) + xdf->type = xd_field_FIXED; + else if(NAD_AVAL_L(nad, attr) == 6 && strncmp("hidden", NAD_AVAL(nad, attr), 6) == 0) + xdf->type = xd_field_HIDDEN; + else if(NAD_AVAL_L(nad, attr) == 9 && strncmp("jid-multi", NAD_AVAL(nad, attr), 9) == 0) + xdf->type = xd_field_JID_MULTI; + else if(NAD_AVAL_L(nad, attr) == 10 && strncmp("jid-single", NAD_AVAL(nad, attr), 10) == 0) + xdf->type = xd_field_JID_SINGLE; + else if(NAD_AVAL_L(nad, attr) == 10 && strncmp("list-multi", NAD_AVAL(nad, attr), 10) == 0) + xdf->type = xd_field_LIST_MULTI; + else if(NAD_AVAL_L(nad, attr) == 11 && strncmp("list-single", NAD_AVAL(nad, attr), 11) == 0) + xdf->type = xd_field_LIST_SINGLE; + else if(NAD_AVAL_L(nad, attr) == 10 && strncmp("text-multi", NAD_AVAL(nad, attr), 10) == 0) + xdf->type = xd_field_TEXT_MULTI; + else if(NAD_AVAL_L(nad, attr) == 12 && strncmp("text-private", NAD_AVAL(nad, attr), 12) == 0) + xdf->type = xd_field_TEXT_PRIVATE; + else if(NAD_AVAL_L(nad, attr) == 11 && strncmp("text-single", NAD_AVAL(nad, attr), 11) == 0) + xdf->type = xd_field_TEXT_SINGLE; + else { + log_debug(ZONE, "unknown field type '%.*s'", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr)); + return NULL; + } + } + + elem = nad_find_elem(nad, root, NAD_ENS(nad, root), "value", 1); + while(elem >= 0) { + if(NAD_CDATA_L(nad, elem) <= 0) { + log_debug(ZONE, "value element requires cdata"); + return NULL; + } + + xdata_add_value(xdf, NAD_CDATA(nad, elem), NAD_CDATA_L(nad, elem)); + + elem = nad_find_elem(nad, elem, NAD_ENS(nad, elem), "value", 0); + } + + elem = nad_find_elem(nad, root, NAD_ENS(nad, root), "options", 1); + while(elem >= 0) { + eval = nad_find_elem(nad, elem, NAD_ENS(nad, elem), "value", 1); + if(eval < 0) { + log_debug(ZONE, "option requires value subelement"); + return NULL; + } + + if(NAD_CDATA_L(nad, eval) <= 0) { + log_debug(ZONE, "value element requires cdata"); + return NULL; + } + + attr = nad_find_attr(nad, elem, -1, "label", NULL); + if(attr < 0) + xdata_option_new(xdf, NAD_CDATA(nad, eval), NAD_CDATA_L(nad, eval), NAD_AVAL(nad, eval), NAD_AVAL_L(nad, eval)); + else + xdata_option_new(xdf, NAD_CDATA(nad, eval), NAD_CDATA_L(nad, eval), NULL, 0); + + elem = nad_find_elem(nad, elem, NAD_ENS(nad, elem), "options", 0); + } + + return xdf; +} + +/** parse a nad and build */ +xdata_t xdata_parse(nad_t nad, int root) { + xdata_t xd; + int atype, elem, field; + xdata_field_t xdf; + + assert((int) nad); + assert((int) (root >= 0)); + + log_debug(ZONE, "building xd from nad"); + + if(root >= nad->ecur || NAD_NURI_L(nad, NAD_ENS(nad, root)) != strlen(uri_XDATA) || strncmp(uri_XDATA, NAD_NURI(nad, NAD_ENS(nad, root)), strlen(uri_XDATA) != 0) || NAD_ENAME_L(nad, root) != 1 || (NAD_ENAME(nad, root))[0] != 'x') { + log_debug(ZONE, "elem %d does not exist, or is not {x:data}x", root); + return NULL; + } + + atype = nad_find_attr(nad, root, -1, "type", NULL); + if(atype < 0) { + log_debug(ZONE, "no type attribute"); + return NULL; + } + + if(NAD_AVAL_L(nad, atype) == 4 && strncmp("form", NAD_AVAL(nad, atype), NAD_AVAL_L(nad, atype)) == 0) + xd = xdata_new(xd_type_FORM, NULL, NULL); + else if(NAD_AVAL_L(nad, atype) == 6 && strncmp("result", NAD_AVAL(nad, atype), NAD_AVAL_L(nad, atype)) == 0) + xd = xdata_new(xd_type_RESULT, NULL, NULL); + else if(NAD_AVAL_L(nad, atype) == 6 && strncmp("submit", NAD_AVAL(nad, atype), NAD_AVAL_L(nad, atype)) == 0) + xd = xdata_new(xd_type_SUBMIT, NULL, NULL); + else if(NAD_AVAL_L(nad, atype) == 6 && strncmp("cancel", NAD_AVAL(nad, atype), NAD_AVAL_L(nad, atype)) == 0) + xd = xdata_new(xd_type_CANCEL, NULL, NULL); + else { + log_debug(ZONE, "unknown xd type %.*s", NAD_AVAL_L(nad, atype), NAD_AVAL(nad, atype)); + return NULL; + } + + elem = nad_find_elem(nad, root, NAD_ENS(nad, root), "title", 1); + if(elem < 0 || NAD_CDATA_L(nad, elem) <= 0) { + log_debug(ZONE, "no cdata on x/title element"); + pool_free(xd->p); + return NULL; + } + + xd->title = pmalloco(xd->p, sizeof(char) * (NAD_CDATA_L(nad, elem) + 1)); + strncpy(xd->title, NAD_CDATA(nad, elem), NAD_CDATA_L(nad, elem)); + + elem = nad_find_elem(nad, root, NAD_ENS(nad, root), "instructions", 1); + if(elem < 0 || NAD_CDATA_L(nad, elem) <= 0) { + log_debug(ZONE, "no cdata on x/instructions element"); + pool_free(xd->p); + return NULL; + } + + xd->instructions = pstrdupx(xd->p, NAD_CDATA(nad, elem), NAD_CDATA_L(nad, elem)); + + switch(xd->type) { + case xd_type_FORM: + case xd_type_SUBMIT: + /* form and submit just have fields, one level */ + field = nad_find_elem(nad, root, NAD_ENS(nad, root), "field", 1); + while(field >= 0) { + xdf = _xdata_field_parse(xd, nad, field); + if(xdf == NULL) { + log_debug(ZONE, "field parse failed"); + pool_free(xd->p); + return NULL; + } + + xdata_add_field(xd, xdf); + + field = nad_find_elem(nad, field, NAD_ENS(nad, root), "field", 0); + } + + break; + + case xd_type_RESULT: + /* result has reported and item */ + elem = nad_find_elem(nad, root, NAD_ENS(nad, root), "reported", 1); + if(elem >= 0) { + field = nad_find_elem(nad, elem, NAD_ENS(nad, root), "field", 1); + while(field >= 0) { + xdf = _xdata_field_parse(xd, nad, field); + if(xdf == NULL) { + log_debug(ZONE, "field parse failed"); + pool_free(xd->p); + return NULL; + } + + xdata_add_field(xd, xdf); + + field = nad_find_elem(nad, field, NAD_ENS(nad, root), "field", 0); + } + } + + elem = nad_find_elem(nad, root, NAD_ENS(nad, root), "item", 1); + if(elem >= 0) { + field = nad_find_elem(nad, elem, NAD_ENS(nad, root), "field", 1); + while(field >= 0) { + xdf = _xdata_field_parse(xd, nad, field); + if(xdf == NULL) { + log_debug(ZONE, "field parse failed"); + pool_free(xd->p); + return NULL; + } + + xdata_add_field(xd, xdf); + + field = nad_find_elem(nad, field, NAD_ENS(nad, root), "field", 0); + } + } + + break; + + case xd_type_CANCEL: + /* nothing to do with cancel, its all based on context */ + break; + + case xd_type_NONE: + break; + } + + return xd; +} diff --git a/util/xdata.h b/util/xdata.h new file mode 100644 index 00000000..4addabaf --- /dev/null +++ b/util/xdata.h @@ -0,0 +1,131 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2003 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/* prototypes for xdata */ + +#ifndef INCL_XDATA_H +#define INCL_XDATA_H + +#include "util.h" + +typedef struct _xdata_st *xdata_t; +typedef struct _xdata_field_st *xdata_field_t; +typedef struct _xdata_option_st *xdata_option_t; +typedef struct _xdata_item_st *xdata_item_t; + +typedef enum { + xd_type_NONE, + xd_type_FORM, + xd_type_RESULT, + xd_type_SUBMIT, + xd_type_CANCEL +} xdata_type_t; + +struct _xdata_st { + pool p; + + xdata_type_t type; + + char *title; + char *instructions; + + xdata_field_t fields, flast; + xdata_field_t rfields, rflast; /* reported fields */ + + xdata_item_t items, ilast; +}; + +typedef enum { + xd_field_NONE, + xd_field_BOOLEAN, + xd_field_FIXED, + xd_field_HIDDEN, + xd_field_JID_MULTI, + xd_field_JID_SINGLE, + xd_field_LIST_MULTI, + xd_field_LIST_SINGLE, + xd_field_TEXT_MULTI, + xd_field_TEXT_PRIVATE, + xd_field_TEXT_SINGLE +} xdata_field_type_t; + +struct _xdata_field_st { + pool p; + + xdata_field_type_t type; + + char *var; + + char *label; + + char *desc; + + int required; + + char **values; + int nvalues; + + xdata_option_t options, olast; + + xdata_field_t next; +}; + +struct _xdata_option_st { + pool p; + + char *label; + char *value; + + xdata_option_t next; +}; + +struct _xdata_item_st { + pool p; + + xdata_field_t fields, flast; + + xdata_item_t next; +}; + +/** creation */ +xdata_t xdata_new(xdata_type_t type, char *title, char *instructions); +xdata_t xdata_parse(nad_t nad, int root); + +/** new field */ +xdata_field_t xdata_field_new(xdata_t xd, xdata_field_type_t type, char *var, char *label, char *desc, int required); + +/** new item */ +xdata_item_t xdata_item_new(xdata_t xd); + +/** field insertion */ +void xdata_add_field(xdata_t xd, xdata_field_t xdf); +void xdata_add_rfield(xdata_t xd, xdata_field_t xdf); +void xdata_add_field_item(xdata_item_t item, xdata_field_t xdf); + +/** item insertion */ +void xdata_add_item(xdata_t xd, xdata_item_t xdi); + +/** option insertion */ +void xdata_add_option(xdata_field_t xdf, char *value, int lvalue, char *label, int llabel); + +/** value insertion */ +void xdata_add_value(xdata_field_t xdf, char *value, int vlen); + +#endif diff --git a/util/xhash.c b/util/xhash.c new file mode 100644 index 00000000..acaae4cd --- /dev/null +++ b/util/xhash.c @@ -0,0 +1,308 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +#include "util.h" + + +/* Generates a hash code for a string. + * This function uses the ELF hashing algorithm as reprinted in + * Andrew Binstock, "Hashing Rehashed," Dr. Dobb's Journal, April 1996. + */ +int _xhasher(const char *s, int len) +{ + /* ELF hash uses unsigned chars and unsigned arithmetic for portability */ + const unsigned char *name = (const unsigned char *)s; + unsigned long h = 0, g; + int i; + + for(i=0;i> 24); + h &= ~g; + + } + + return (int)h; +} + + +xhn _xhash_node_new(xht h, int index) +{ + xhn n; + int i = index % h->prime; + + /* track total */ + h->count++; + + /* get existing empty one */ + for(n = &h->zen[i]; n != NULL; n = n->next) + if(n->key == NULL) + return n; + + /* overflowing, new one! */ + n = pmalloco(h->p, sizeof(_xhn)); + n->next = h->zen[i].next; + h->zen[i].next = n; + return n; +} + + +xhn _xhash_node_get(xht h, const char *key, int len, int index) +{ + xhn n; + int i = index % h->prime; + for(n = &h->zen[i]; n != NULL; n = n->next) + if(n->key != NULL && (strlen(n->key)==len) && (strncmp(key, n->key, len) == 0)) + return n; + return NULL; +} + + +xht xhash_new(int prime) +{ + xht xnew; + pool p; + +/* log_debug(ZONE,"creating new hash table of size %d",prime); */ + + p = pool_heap(sizeof(_xhn)*prime + sizeof(_xht)); + xnew = pmalloco(p, sizeof(_xht)); + xnew->prime = prime; + xnew->p = p; + xnew->zen = pmalloco(p, sizeof(_xhn)*prime); /* array of xhn size of prime */ + + xnew->iter_bucket = -1; + xnew->iter_node = NULL; + + return xnew; +} + + +void xhash_putx(xht h, const char *key, int len, void *val) +{ + int index; + xhn n; + + if(h == NULL || key == NULL) + return; + + index = _xhasher(key,len); + + /* dirty the xht */ + h->dirty++; + + /* if existing key, replace it */ + if((n = _xhash_node_get(h, key, len, index)) != NULL) + { +/* log_debug(ZONE,"replacing %s with new val %X",key,val); */ + + n->key = key; + n->val = val; + return; + } + +/* log_debug(ZONE,"saving %s val %X",key,val); */ + + /* new node */ + n = _xhash_node_new(h, index); + n->key = key; + n->val = val; +} + +void xhash_put(xht h, const char *key, void *val) +{ + if(h == NULL || key == NULL) return; + xhash_putx(h,key,strlen(key),val); +} + + +void *xhash_getx(xht h, const char *key, int len) +{ + xhn n; + + if(h == NULL || key == NULL || len <= 0 || (n = _xhash_node_get(h, key, len, _xhasher(key,len))) == NULL) + { +/* log_debug(ZONE,"failed lookup of %s",key); */ + return NULL; + } + +/* log_debug(ZONE,"found %s returning %X",key,n->val); */ + return n->val; +} + +void *xhash_get(xht h, const char *key) +{ + if(h == NULL || key == NULL) return NULL; + return xhash_getx(h,key,strlen(key)); +} + + +void xhash_zapx(xht h, const char *key, int len) +{ + xhn n; + + if(h == NULL || key == NULL || (n = _xhash_node_get(h, key, len, _xhasher(key,len))) == NULL) + return; + +/* log_debug(ZONE,"zapping %s",key); */ + + /* kill an entry by zeroing out the key */ + n->key = NULL; + n->val = NULL; + + /* dirty the xht and track the total */ + h->dirty++; + h->count--; + + /* if we just killed the current iter, move to the next one */ + if(h->iter_node == n) + xhash_iter_next(h); +} + +void xhash_zap(xht h, const char *key) +{ + if(h == NULL || key == NULL) return; + xhash_zapx(h,key,strlen(key)); +} + +void xhash_free(xht h) +{ +/* log_debug(ZONE,"hash free %X",h); */ + + if(h != NULL) + pool_free(h->p); +} + +void xhash_walk(xht h, xhash_walker w, void *arg) +{ + int i; + xhn n; + + if(h == NULL || w == NULL) + return; + +/* log_debug(ZONE,"walking %X",h); */ + + for(i = 0; i < h->prime; i++) + for(n = &h->zen[i]; n != NULL; n = n->next) + if(n->key != NULL && n->val != NULL) + (*w)(h, n->key, n->val, arg); +} + +/** return the dirty flag (and reset) */ +int xhash_dirty(xht h) +{ + int dirty; + + if(h == NULL) return 1; + + dirty = h->dirty; + h->dirty = 0; + return dirty; +} + +/** return the total number of entries in this xht */ +int xhash_count(xht h) +{ + if(h == NULL) return 0; + + return h->count; +} + +/** get our pool */ +pool xhash_pool(xht h) +{ + return h->p; +} + +/** iteration */ +int xhash_iter_first(xht h) { + if(h == NULL) return 0; + + h->iter_bucket = -1; + h->iter_node = NULL; + + return xhash_iter_next(h); +} + +int xhash_iter_next(xht h) { + if(h == NULL) return 0; + + /* next in this bucket */ + while(h->iter_node != NULL) { + h->iter_node = h->iter_node->next; + + if(h->iter_node != NULL && h->iter_node->key != NULL && h->iter_node->val != NULL) + return 1; + } + + /* next bucket */ + for(h->iter_bucket++; h->iter_bucket < h->prime; h->iter_bucket++) { + h->iter_node = &h->zen[h->iter_bucket]; + + while(h->iter_node != NULL) { + if(h->iter_node->key != NULL && h->iter_node->val != NULL) + return 1; + + h->iter_node = h->iter_node->next; + } + } + + /* there is no next */ + h->iter_bucket = -1; + h->iter_node = NULL; + + return 0; +} + +void xhash_iter_zap(xht h) { + if(h == NULL) return; + + if(h->iter_node == NULL) + return; + + /* pow */ + h->iter_node->key = NULL; + h->iter_node->val = NULL; + + /* dirty the xht and track the total */ + h->dirty++; + h->count--; + + /* next one */ + xhash_iter_next(h); +} + +int xhash_iter_get(xht h, const char **key, void **val) { + if(h == NULL || (key == NULL && val == NULL)) return 0; + + if(h->iter_node == NULL) { + if(key != NULL) *key = NULL; + if(val != NULL) *val = NULL; + return 0; + } + + if(key != NULL) *key = h->iter_node->key; + if(val != NULL) *val = h->iter_node->val; + + return 1; +} diff --git a/util/xhash.h b/util/xhash.h new file mode 100644 index 00000000..49b30680 --- /dev/null +++ b/util/xhash.h @@ -0,0 +1,58 @@ +/* + * jabberd - Jabber Open Source Server + * Copyright (c) 2002-2004 Jeremie Miller, Thomas Muldowney, + * Ryan Eatmon, Robert Norris + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + */ + +/** @file util/xhash.h + * @brief hashtables + * $Date: 2004/04/30 00:53:55 $ + * $Revision: 1.1 $ + */ + +#ifndef INCL_UTIL_XHASH_H +#define INCL_UTIL_XHASH_H 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "pool.h" + +/* opaque decl */ +typedef struct _xhash_st *xhash_t; + +typedef void (*xhash_walker_t)(xhash_t h, const char *key, void *val, void *arg); + +xhash_t xhash_new(pool_t p, int prime); +void xhash_free(xhash_t h); +pool_t xhash_pool(xhash_t h); +void xhash_put(xhash_t h, const char *key, void *val); +void xhash_putx(xhash_t h, const char *key, int len, void *val); +void *xhash_get(xhash_t h, const char *key); +void *xhash_getx(xhash_t h, const char *key, int len); +void xhash_zap(xhash_t h, const char *key); +void xhash_zapx(xhash_t h, const char *key, int len); +void xhash_walk(xhash_t h, xhash_walker_t fn, void *arg); +int xhash_dirty(xhash_t h); +int xhash_count(xhash_t h); +int xhash_iter_first(xhash_t h); +int xhash_iter_next(xhash_t h); +void xhash_iter_zap(xhash_t h); +void xhash_iter_get(xhash_t h, const char **key, void **val); + +#endif