diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
commit | af754e596a8dbb05ed8580c342e7fe02e08b28e0 (patch) | |
tree | b2f334c2b55ede42081aa6710a72da784547d8ea /src/lib | |
parent | Initial commit. (diff) | |
download | freeradius-af754e596a8dbb05ed8580c342e7fe02e08b28e0.tar.xz freeradius-af754e596a8dbb05ed8580c342e7fe02e08b28e0.zip |
Adding upstream version 3.2.3+dfsg.upstream/3.2.3+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
43 files changed, 31348 insertions, 0 deletions
diff --git a/src/lib/LICENSE b/src/lib/LICENSE new file mode 100644 index 0000000..fffbb23 --- /dev/null +++ b/src/lib/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/src/lib/README b/src/lib/README new file mode 100644 index 0000000..1c1da3a --- /dev/null +++ b/src/lib/README @@ -0,0 +1,2 @@ + The files in this directory are placed under the LGPL, as given +in the LICENSE file in this directory. diff --git a/src/lib/all.mk b/src/lib/all.mk new file mode 100644 index 0000000..eef85d7 --- /dev/null +++ b/src/lib/all.mk @@ -0,0 +1,53 @@ +# +# Makefile +# +# Version: $Id$ +# +TARGET := libfreeradius-radius.a + +SOURCES := cbuff.c \ + cursor.c \ + debug.c \ + dict.c \ + filters.c \ + hash.c \ + hmacmd5.c \ + hmacsha1.c \ + isaac.c \ + log.c \ + misc.c \ + missing.c \ + md4.c \ + md5.c \ + net.c \ + pair.c \ + pcap.c \ + print.c \ + radius.c \ + rbtree.c \ + regex.c \ + sha1.c \ + snprintf.c \ + strlcat.c \ + strlcpy.c \ + socket.c \ + token.c \ + udpfromto.c \ + value.c \ + fifo.c \ + packet.c \ + event.c \ + getaddrinfo.c \ + heap.c \ + tcp.c \ + base64.c \ + version.c \ + atomic_queue.c \ + talloc.c + +SRC_CFLAGS := -D_LIBRADIUS -I$(top_builddir)/src + +# System libraries discovered by our top level configure script, links things +# like pthread and the regexp libraries. +TGT_LDLIBS := $(LIBS) $(PCAP_LIBS) +TGT_LDFLAGS := $(LDFLAGS) $(PCAP_LDFLAGS) diff --git a/src/lib/atomic_queue.c b/src/lib/atomic_queue.c new file mode 100644 index 0000000..cece3c4 --- /dev/null +++ b/src/lib/atomic_queue.c @@ -0,0 +1,337 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @brief Thread-safe queues. + * @file atomic_queue.c + * + * @copyright 2016 Alan DeKok (aland@freeradius.org) + * @copyright 2016 Alister Winfield + */ +RCSID("$Id$") + +#ifdef HAVE_STDALIGN_H + +#include <stdint.h> +#include <stdalign.h> +#include <inttypes.h> + +#include <freeradius-devel/autoconf.h> + +#include <freeradius-devel/atomic_queue.h> +#include <freeradius-devel/talloc.h> + +#define CACHE_LINE_SIZE 64 + +/** Entry in the queue + * + * @note This structure is cache line aligned for modern AMD/Intel CPUs. + * This is to avoid contention when the producer and consumer are executing + * on different CPU cores. + */ +typedef struct CC_HINT(packed, aligned(CACHE_LINE_SIZE)) { + atomic_int64_t seq; //!< Must be seq then data to ensure + ///< seq is 64bit aligned for 32bit address + ///< spaces. + void *data; +} fr_atomic_queue_entry_t; + +/** Structure to hold the atomic queue + * + */ +struct fr_atomic_queue_s { + alignas(CACHE_LINE_SIZE) atomic_int64_t head; //!< Head, aligned bytes to ensure + ///< it's in a different cache line to tail + ///< to reduce memory contention. + atomic_int64_t tail; + + size_t size; + + void *chunk; //!< To pass to free. The non-aligned address. + + alignas(CACHE_LINE_SIZE) fr_atomic_queue_entry_t entry[]; //!< The entry array, also aligned + ///< to ensure it's not in the same cache + ///< line as tail and size. +}; + +/** Create fixed-size atomic queue + * + * @note the queue must be freed explicitly by the ctx being freed, or by using + * the #fr_atomic_queue_free function. + * + * @param[in] ctx The talloc ctx to allocate the queue in. + * @param[in] size The number of entries in the queue. + * @return + * - NULL on error. + * - fr_atomic_queue_t *, a pointer to the allocated and initialized queue. + */ +fr_atomic_queue_t *fr_atomic_queue_alloc(TALLOC_CTX *ctx, size_t size) +{ + size_t i; + int64_t seq; + fr_atomic_queue_t *aq; + TALLOC_CTX *chunk; + + if (size == 0) return NULL; + + /* + * Allocate a contiguous blob for the header and queue. + * This helps with memory locality. + * + * Since we're allocating a blob, we should also set the + * name of the data, too. + */ + chunk = talloc_aligned_array(ctx, (void **)&aq, CACHE_LINE_SIZE, + sizeof(*aq) + (size) * sizeof(aq->entry[0])); + if (!chunk) return NULL; + aq->chunk = chunk; + + talloc_set_name_const(chunk, "fr_atomic_queue_t"); + + /* + * Initialize the array. Data is NULL, and indexes are + * the array entry number. + */ + for (i = 0; i < size; i++) { + seq = i; + + aq->entry[i].data = NULL; + store(aq->entry[i].seq, seq); + } + + aq->size = size; + + /* + * Set the head / tail indexes, and force other CPUs to + * see the writes. + */ + store(aq->head, 0); + store(aq->tail, 0); + atomic_thread_fence(memory_order_seq_cst); + + return aq; +} + +/** Free an atomic queue if it's not freed by ctx + * + * This function is needed because the atomic queue memory + * must be cache line aligned. + */ +void fr_atomic_queue_free(fr_atomic_queue_t **aq) +{ + if (!*aq) return; + + talloc_free((*aq)->chunk); + *aq = NULL; +} + +/** Push a pointer into the atomic queue + * + * @param[in] aq The atomic queue to add data to. + * @param[in] data to push. + * @return + * - true on successful push + * - false on queue full + */ +bool fr_atomic_queue_push(fr_atomic_queue_t *aq, void *data) +{ + int64_t head; + fr_atomic_queue_entry_t *entry; + + if (!data) return false; + + head = load(aq->head); + + /* + * Try to find the current head. + */ + for (;;) { + int64_t seq, diff; + + entry = &aq->entry[ head % aq->size ]; + seq = aquire(entry->seq); + diff = (seq - head); + + /* + * head is larger than the current entry, the queue is full. + */ + if (diff < 0) { +#if 0 + fr_atomic_queue_debug(aq, stderr); +#endif + return false; + } + + /* + * Someone else has already written to this entry. Get the new head pointer, and continue. + */ + if (diff > 0) { + head = load(aq->head); + continue; + } + + /* + * We have the possibility that we can write to + * this entry. Try it. If the write succeeds, + * we're done. If the write fails, re-load the + * current head entry, and continue. + */ + if (cas_incr(aq->head, head)) { + break; + } + } + + /* + * Store the data in the queue, and increment the entry + * with the new index, and make the write visible to + * other CPUs. + */ + entry->data = data; + store(entry->seq, head + 1); + return true; +} + + +/** Pop a pointer from the atomic queue + * + * @param[in] aq the atomic queue to retrieve data from. + * @param[out] p_data where to write the data. + * @return + * - true on successful pop + * - false on queue empty + */ +bool fr_atomic_queue_pop(fr_atomic_queue_t *aq, void **p_data) +{ + int64_t tail, seq; + fr_atomic_queue_entry_t *entry; + + if (!p_data) return false; + + tail = load(aq->tail); + + for (;;) { + int64_t diff; + + entry = &aq->entry[ tail % aq->size ]; + seq = aquire(entry->seq); + + diff = (seq - (tail + 1)); + + /* + * tail is smaller than the current entry, the queue is full. + */ + if (diff < 0) { + return false; + } + + if (diff > 0) { + tail = load(aq->tail); + continue; + } + + if (cas_incr(aq->tail, tail)) { + break; + } + } + + /* + * Copy the pointer to the caller BEFORE updating the + * queue entry. + */ + *p_data = entry->data; + + /* + * Set the current entry to past the end of the queue. + * i.e. it's unused. + */ + seq = tail + aq->size; + store(entry->seq, seq); + + return true; +} + +size_t fr_atomic_queue_size(fr_atomic_queue_t *aq) +{ + return aq->size; +} + +#ifdef WITH_VERIFY_PTR +/** Check the talloc chunk is still valid + * + */ +void fr_atomic_queue_verify(fr_atomic_queue_t *aq) +{ + (void)talloc_get_type_abort(aq->chunk, fr_atomic_queue_t); +} +#endif + +#ifndef NDEBUG + +#if 0 +typedef struct { + int status; //!< status of this message + size_t data_size; //!< size of the data we're sending + + int signal; //!< the signal to send + uint64_t ack; //!< or the endpoint.. + void *ch; //!< the channel +} fr_control_message_t; +#endif + + +/** Dump an atomic queue. + * + * Absolutely NOT thread-safe. + * + * @param[in] aq The atomic queue to debug. + * @param[in] fp where the debugging information will be printed. + */ +void fr_atomic_queue_debug(fr_atomic_queue_t *aq, FILE *fp) +{ + size_t i; + int64_t head, tail; + + head = load(aq->head); + tail = load(aq->head); + + fprintf(fp, "AQ %p size %zu, head %" PRId64 ", tail %" PRId64 "\n", + aq, aq->size, head, tail); + + for (i = 0; i < aq->size; i++) { + fr_atomic_queue_entry_t *entry; + + entry = &aq->entry[i]; + + fprintf(fp, "\t[%zu] = { %p, %" PRId64 " }", + i, entry->data, load(entry->seq)); +#if 0 + if (entry->data) { + fr_control_message_t *c; + + c = entry->data; + + fprintf(fp, "\tstatus %d, data_size %zd, signal %d, ack %zd, ch %p", + c->status, c->data_size, c->signal, c->ack, c->ch); + } +#endif + fprintf(fp, "\n"); + } +} +#endif + +#endif /* HAVE_STDALIGN_H */ diff --git a/src/lib/base64.c b/src/lib/base64.c new file mode 100644 index 0000000..6b40866 --- /dev/null +++ b/src/lib/base64.c @@ -0,0 +1,315 @@ +/* + * Copyright (C) 1999, 2000, 2001, 2004, 2005, 2006 Free Software + * Foundation, Inc. + * + * This program is left 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, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * @brief Encode/decode binary data using printable characters. + * @author Simon Josefsson. + * @see RFC 3548 <http://www.ietf.org/rfc/rfc3548.txt>. + */ +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/base64.h> + +#define us(x) (uint8_t) x + +/** Base 64 encode binary data + * + * Base64 encode IN array of size INLEN into OUT array of size OUTLEN. + * + * @param[out] out Where to write Base64 string. + * @param[in] outlen size of buffer including NULL byte. + * @param[in] in Data to encode. + * @param[in] inlen Length of data to encode. + * @return The amount of data we wrote to the buffer or -1 if output buffer + * was too small. + */ +ssize_t fr_base64_encode(char *out, size_t outlen, uint8_t const *in, size_t inlen) +{ + static char const b64str[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + char *p = out; + if (outlen < (FR_BASE64_ENC_LENGTH(inlen) + 1)) { + *out = '\0'; + return -1; + } + + while (inlen) { + *p++ = b64str[(in[0] >> 2) & 0x3f]; + *p++ = b64str[((in[0] << 4) + (--inlen ? in[1] >> 4 : 0)) & 0x3f]; + *p++ = (inlen ? b64str[((in[1] << 2) + (--inlen ? in[2] >> 6 : 0)) & 0x3f] : '='); + *p++ = inlen ? b64str[in[2] & 0x3f] : '='; + + if (inlen) inlen--; + if (inlen) in += 3; + } + + p[0] = '\0'; + + return p - out; +} + +/* + * With this approach this file works independent of the charset used + * (think EBCDIC). However, it does assume that the characters in the + * Base64 alphabet (A-Za-z0-9+/) are encoded in 0..255. POSIX + * 1003.1-2001 require that char and unsigned char are 8-bit + * quantities, though, taking care of that problem. But this may be a + * potential problem on non-POSIX C99 platforms. + * + * IBM C V6 for AIX mishandles "#define B64(x) ...'x'...", so use "_" + * as the formal parameter rather than "x". + */ +#define B64(_) \ + ((_) == 'A' ? 0 \ + : (_) == 'B' ? 1 \ + : (_) == 'C' ? 2 \ + : (_) == 'D' ? 3 \ + : (_) == 'E' ? 4 \ + : (_) == 'F' ? 5 \ + : (_) == 'G' ? 6 \ + : (_) == 'H' ? 7 \ + : (_) == 'I' ? 8 \ + : (_) == 'J' ? 9 \ + : (_) == 'K' ? 10 \ + : (_) == 'L' ? 11 \ + : (_) == 'M' ? 12 \ + : (_) == 'N' ? 13 \ + : (_) == 'O' ? 14 \ + : (_) == 'P' ? 15 \ + : (_) == 'Q' ? 16 \ + : (_) == 'R' ? 17 \ + : (_) == 'S' ? 18 \ + : (_) == 'T' ? 19 \ + : (_) == 'U' ? 20 \ + : (_) == 'V' ? 21 \ + : (_) == 'W' ? 22 \ + : (_) == 'X' ? 23 \ + : (_) == 'Y' ? 24 \ + : (_) == 'Z' ? 25 \ + : (_) == 'a' ? 26 \ + : (_) == 'b' ? 27 \ + : (_) == 'c' ? 28 \ + : (_) == 'd' ? 29 \ + : (_) == 'e' ? 30 \ + : (_) == 'f' ? 31 \ + : (_) == 'g' ? 32 \ + : (_) == 'h' ? 33 \ + : (_) == 'i' ? 34 \ + : (_) == 'j' ? 35 \ + : (_) == 'k' ? 36 \ + : (_) == 'l' ? 37 \ + : (_) == 'm' ? 38 \ + : (_) == 'n' ? 39 \ + : (_) == 'o' ? 40 \ + : (_) == 'p' ? 41 \ + : (_) == 'q' ? 42 \ + : (_) == 'r' ? 43 \ + : (_) == 's' ? 44 \ + : (_) == 't' ? 45 \ + : (_) == 'u' ? 46 \ + : (_) == 'v' ? 47 \ + : (_) == 'w' ? 48 \ + : (_) == 'x' ? 49 \ + : (_) == 'y' ? 50 \ + : (_) == 'z' ? 51 \ + : (_) == '0' ? 52 \ + : (_) == '1' ? 53 \ + : (_) == '2' ? 54 \ + : (_) == '3' ? 55 \ + : (_) == '4' ? 56 \ + : (_) == '5' ? 57 \ + : (_) == '6' ? 58 \ + : (_) == '7' ? 59 \ + : (_) == '8' ? 60 \ + : (_) == '9' ? 61 \ + : (_) == '+' ? 62 \ + : (_) == '/' ? 63 \ + : -1) + +static const signed char b64[0x100] = { + B64 (0), B64 (1), B64 (2), B64 (3), + B64 (4), B64 (5), B64 (6), B64 (7), + B64 (8), B64 (9), B64 (10), B64 (11), + B64 (12), B64 (13), B64 (14), B64 (15), + B64 (16), B64 (17), B64 (18), B64 (19), + B64 (20), B64 (21), B64 (22), B64 (23), + B64 (24), B64 (25), B64 (26), B64 (27), + B64 (28), B64 (29), B64 (30), B64 (31), + B64 (32), B64 (33), B64 (34), B64 (35), + B64 (36), B64 (37), B64 (38), B64 (39), + B64 (40), B64 (41), B64 (42), B64 (43), + B64 (44), B64 (45), B64 (46), B64 (47), + B64 (48), B64 (49), B64 (50), B64 (51), + B64 (52), B64 (53), B64 (54), B64 (55), + B64 (56), B64 (57), B64 (58), B64 (59), + B64 (60), B64 (61), B64 (62), B64 (63), + B64 (64), B64 (65), B64 (66), B64 (67), + B64 (68), B64 (69), B64 (70), B64 (71), + B64 (72), B64 (73), B64 (74), B64 (75), + B64 (76), B64 (77), B64 (78), B64 (79), + B64 (80), B64 (81), B64 (82), B64 (83), + B64 (84), B64 (85), B64 (86), B64 (87), + B64 (88), B64 (89), B64 (90), B64 (91), + B64 (92), B64 (93), B64 (94), B64 (95), + B64 (96), B64 (97), B64 (98), B64 (99), + B64 (100), B64 (101), B64 (102), B64 (103), + B64 (104), B64 (105), B64 (106), B64 (107), + B64 (108), B64 (109), B64 (110), B64 (111), + B64 (112), B64 (113), B64 (114), B64 (115), + B64 (116), B64 (117), B64 (118), B64 (119), + B64 (120), B64 (121), B64 (122), B64 (123), + B64 (124), B64 (125), B64 (126), B64 (127), + B64 (128), B64 (129), B64 (130), B64 (131), + B64 (132), B64 (133), B64 (134), B64 (135), + B64 (136), B64 (137), B64 (138), B64 (139), + B64 (140), B64 (141), B64 (142), B64 (143), + B64 (144), B64 (145), B64 (146), B64 (147), + B64 (148), B64 (149), B64 (150), B64 (151), + B64 (152), B64 (153), B64 (154), B64 (155), + B64 (156), B64 (157), B64 (158), B64 (159), + B64 (160), B64 (161), B64 (162), B64 (163), + B64 (164), B64 (165), B64 (166), B64 (167), + B64 (168), B64 (169), B64 (170), B64 (171), + B64 (172), B64 (173), B64 (174), B64 (175), + B64 (176), B64 (177), B64 (178), B64 (179), + B64 (180), B64 (181), B64 (182), B64 (183), + B64 (184), B64 (185), B64 (186), B64 (187), + B64 (188), B64 (189), B64 (190), B64 (191), + B64 (192), B64 (193), B64 (194), B64 (195), + B64 (196), B64 (197), B64 (198), B64 (199), + B64 (200), B64 (201), B64 (202), B64 (203), + B64 (204), B64 (205), B64 (206), B64 (207), + B64 (208), B64 (209), B64 (210), B64 (211), + B64 (212), B64 (213), B64 (214), B64 (215), + B64 (216), B64 (217), B64 (218), B64 (219), + B64 (220), B64 (221), B64 (222), B64 (223), + B64 (224), B64 (225), B64 (226), B64 (227), + B64 (228), B64 (229), B64 (230), B64 (231), + B64 (232), B64 (233), B64 (234), B64 (235), + B64 (236), B64 (237), B64 (238), B64 (239), + B64 (240), B64 (241), B64 (242), B64 (243), + B64 (244), B64 (245), B64 (246), B64 (247), + B64 (248), B64 (249), B64 (250), B64 (251), + B64 (252), B64 (253), B64 (254), B64 (255) +}; + +/** Check if char is in Base64 alphabet + * + * Note that '=' is padding and not considered to be part of the alphabet. + * + * @param c char to check. + * @return true if CH is a character from the Base64 alphabet, and false + * otherwise. + */ +bool fr_is_base64(char c) +{ + return b64[us(c)] >= 0; +} + +/* Decode base64 encoded input array. + * + * Decode base64 encoded input array IN of length INLEN to output array OUT that + * can hold *OUTLEN bytes. Return true if decoding was successful, i.e. + * if the input was valid base64 data, -1 otherwise. + * + * If *OUTLEN is too small, as many bytes as possible will be written to OUT. + * On return, *OUTLEN holds the length of decoded bytes in OUT. + * + * Note that as soon as any non-alphabet characters are encountered, + * decoding is stopped and -1 is returned. + * + * This means that, when applicable, you must remove any line terminators + * that is part of the data stream before calling this function. + * + * @param[out] out Where to write the decoded data. + * @param[in] outlen The length of the output buffer. + * @param[in] in Base64 string to decode. + * @param[in] inlen length of Base64 string. + * @return -1 on error, else the length of decoded data. + */ +ssize_t fr_base64_decode(uint8_t *out, size_t outlen, char const *in, size_t inlen) +{ + uint8_t *out_p = out; + uint8_t *out_end = out + outlen; + char const *p = in, *q; + char const *end = p + inlen; + + /* + * Process complete 24bit quanta + */ + while ((end - p) >= 4) { + if (!fr_is_base64(p[0]) || !fr_is_base64(p[1]) || !fr_is_base64(p[2]) || !fr_is_base64(p[3])) break; + + /* + * Check we have enough bytes to write out + * the 24bit quantum. + */ + if ((out_end - out_p) <= 3) { + oob: + fr_strerror_printf("Output buffer too small, needed at least %zu bytes", outlen + 1); + return p - end; + } + + *out_p++ = ((b64[us(p[0])] << 2) | (b64[us(p[1])] >> 4)); + *out_p++ = ((b64[us(p[1])] << 4) & 0xf0) | (b64[us(p[2])] >> 2); + *out_p++ = ((b64[us(p[2])] << 6) & 0xc0) | b64[us(p[3])]; + + p += 4; /* 32bit input -> 24bit output */ + } + + q = p; + + /* + * Find the first non-base64 char + */ + while ((q < end) && fr_is_base64(*q)) q++; + + switch (q - p) { + case 0: /* Final quantum is 24 bits */ + break; + + case 2: /* Final quantum is 8 bits */ + if ((out_end - out_p) < 1) goto oob; + *out_p++ = ((b64[us(p[0])] << 2) | (b64[us(p[1])] >> 4)); + p += 2; + break; + + case 3: /* Final quantum is 16 bits */ + if ((out_end - out_p) < 2) goto oob; + *out_p++ = ((b64[us(p[0])] << 2) | (b64[us(p[1])] >> 4)); + *out_p++ = ((b64[us(p[1])] << 4) & 0xf0) | (b64[us(p[2])] >> 2); + p += 3; + break; + + default: + fr_strerror_printf("Invalid base64 padding data"); + return p - end; + } + + while (p < end) { + if (*p != '=') { + fr_strerror_printf("Found non-padding char '%c' at end of base64 string", *p); + return p - end; + } + p++; + } + + return out_p - out; +} diff --git a/src/lib/cbuff.c b/src/lib/cbuff.c new file mode 100644 index 0000000..32646ca --- /dev/null +++ b/src/lib/cbuff.c @@ -0,0 +1,145 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file cbuff.c + * @brief Implementation of a ring buffer + * + * @copyright 2013 The FreeRADIUS server project + * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org> + */ +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#ifdef HAVE_PTHREAD_H +# define PTHREAD_MUTEX_LOCK(_x) if (_x->lock) pthread_mutex_lock(&((_x)->mutex)) +# define PTHREAD_MUTEX_UNLOCK(_x) if (_x->lock) pthread_mutex_unlock(&((_x)->mutex)) +#else +# define PTHREAD_MUTEX_LOCK(_x) +# define PTHREAD_MUTEX_UNLOCK(_x) +#endif + +/** Standard thread safe circular buffer + * + */ +struct fr_cbuff { + void const *end; //!< End of allocated memory + + uint32_t size; + uint32_t in; //!< Write index + uint32_t out; //!< Read index + + void **elem; //!< Ring buffer data + + bool lock; //!< Perform thread synchronisation + pthread_mutex_t mutex; //!< Thread synchronisation mutex +}; + +/** Initialise a new circular buffer + * + * @param ctx to allocate the buffer in. + * @param size of buffer to allocate. + * @param lock If true, insert and next operations will lock the buffer. + * @return new cbuff, or NULL on error. + */ +#ifdef HAVE_PTHREAD_H +fr_cbuff_t *fr_cbuff_alloc(TALLOC_CTX *ctx, uint32_t size, bool lock) +#else +fr_cbuff_t *fr_cbuff_alloc(TALLOC_CTX *ctx, uint32_t size, UNUSED bool lock) +#endif +{ + fr_cbuff_t *cbuff; + + uint32_t pow; + + /* + * Find the nearest power of 2 (rounding up) + */ + for (pow = 0x00000001; + pow < size; + pow <<= 1); + size = pow; + size--; + + cbuff = talloc_zero(ctx, fr_cbuff_t); + if (!cbuff) { + return NULL; + } + cbuff->elem = talloc_zero_array(cbuff, void *, size); + if (!cbuff->elem) { + return NULL; + } + cbuff->size = size; + +#ifdef HAVE_PTHREAD_H + if (lock) { + cbuff->lock = true; + pthread_mutex_init(&cbuff->mutex, NULL); + } +#endif + return cbuff; +} + +/** Insert a new element into the buffer, and steal it from it's original context + * + * cbuff will steal obj and insert it into it's own context. + * + * @param cbuff to insert element into + * @param obj to insert, must of been allocated with talloc + */ +void fr_cbuff_rp_insert(fr_cbuff_t *cbuff, void *obj) +{ + PTHREAD_MUTEX_LOCK(cbuff); + + if (cbuff->elem[cbuff->in]) { + TALLOC_FREE(cbuff->elem[cbuff->in]); + } + + cbuff->elem[cbuff->in] = talloc_steal(cbuff, obj); + + cbuff->in = (cbuff->in + 1) & cbuff->size; + + /* overwrite - out is advanced ahead of in */ + if (cbuff->in == cbuff->out) { + cbuff->out = (cbuff->out + 1) & cbuff->size; + } + + PTHREAD_MUTEX_UNLOCK(cbuff); +} + +/** Remove an item from the buffer, and reparent to ctx + * + * @param cbuff to remove element from + * @param ctx to hang obj off. + * @return NULL if no elements in the buffer, else an element from the buffer reparented to ctx. + */ +void *fr_cbuff_rp_next(fr_cbuff_t *cbuff, TALLOC_CTX *ctx) +{ + void *obj = NULL; + + PTHREAD_MUTEX_LOCK(cbuff); + + /* Buffer is empty */ + if (cbuff->out == cbuff->in) goto done; + + obj = talloc_steal(ctx, cbuff->elem[cbuff->out]); + cbuff->out = (cbuff->out + 1) & cbuff->size; + +done: + PTHREAD_MUTEX_UNLOCK(cbuff); + return obj; +} diff --git a/src/lib/cursor.c b/src/lib/cursor.c new file mode 100644 index 0000000..ae88ebe --- /dev/null +++ b/src/lib/cursor.c @@ -0,0 +1,461 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 of the + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file cursor.c + * @brief Functions to iterate over collections of VALUE_PAIRs + * + * @note Do not modify collections of VALUE_PAIRs pointed to be a cursor + * with none fr_cursor_* functions, during the lifetime of that cursor. + * + * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org> + * @copyright 2013-2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org> + * @copyright 2013-2015 The FreeRADIUS Server Project. + */ + +#include <freeradius-devel/libradius.h> + +/** Internal function to update cursor state + * + * @param cursor to operate on. + * @param vp to set current and found positions to. + * @return value passed in as vp. + */ +inline static VALUE_PAIR *fr_cursor_update(vp_cursor_t *cursor, VALUE_PAIR *vp) +{ + if (!vp) { + cursor->next = NULL; + cursor->current = NULL; + + return NULL; + } + + cursor->next = vp->next; + cursor->current = vp; + cursor->found = vp; + + return vp; +} + +/** Setup a cursor to iterate over attribute pairs + * + * @param cursor Where to initialise the cursor (uses existing structure). + * @param const_vp to start from. + * @return the attribute pointed to by vp. + */ +VALUE_PAIR *fr_cursor_init(vp_cursor_t *cursor, VALUE_PAIR * const *const_vp) +{ + VALUE_PAIR **vp; + + if (!const_vp || !cursor) { + return NULL; + } + + memset(cursor, 0, sizeof(*cursor)); + + memcpy(&vp, &const_vp, sizeof(vp)); /* stupid const hacks */ + + /* + * Useful check to see if uninitialised memory is pointed + * to by vp + */ +#ifndef NDEBUG + if (*vp) VERIFY_VP(*vp); +#endif + memcpy(&cursor->first, &vp, sizeof(cursor->first)); + cursor->current = *cursor->first; + + if (cursor->current) { + VERIFY_VP(cursor->current); + cursor->next = cursor->current->next; + } + + return cursor->current; +} + +/** Copy a cursor + * + * @param in Cursor to copy. + * @param out Where to copy the cursor to. + */ +void fr_cursor_copy(vp_cursor_t *out, vp_cursor_t *in) +{ + memcpy(out, in, sizeof(*out)); +} + +/** Rewind cursor to the start of the list + * + * @param cursor to operate on. + * @return the VALUE_PAIR at the start of the list. + */ +VALUE_PAIR *fr_cursor_first(vp_cursor_t *cursor) +{ + if (!cursor->first) return NULL; + + cursor->current = *cursor->first; + + if (cursor->current) { + VERIFY_VP(cursor->current); + cursor->next = cursor->current->next; + if (cursor->next) VERIFY_VP(cursor->next); + cursor->found = NULL; + } + + return cursor->current; +} + +/** Wind cursor to the last pair in the list + * + * @param cursor to operate on. + * @return the VALUE_PAIR at the end of the list. + */ +VALUE_PAIR *fr_cursor_last(vp_cursor_t *cursor) +{ + if (!cursor->first || !*cursor->first) return NULL; + + /* Need to start at the start */ + if (!cursor->current) fr_cursor_first(cursor); + + /* Wind to the end */ + while (cursor->next) fr_cursor_next(cursor); + + return cursor->current; +} + +/** Iterate over a collection of VALUE_PAIRs of a given type in the pairlist + * + * Find the next attribute of a given type. If no fr_cursor_next_by_* function + * has been called on a cursor before, or the previous call returned + * NULL, the search will start with the current attribute. Subsequent calls to + * fr_cursor_next_by_* functions will start the search from the previously + * matched attribute. + * + * @param cursor to operate on. + * @param attr number to match. + * @param vendor number to match (0 for none vendor attribute). + * @param tag to match. Either a tag number or TAG_ANY to match any tagged or + * untagged attribute, TAG_NONE to match attributes without tags. + * @return the next matching VALUE_PAIR, or NULL if no VALUE_PAIRs match. + */ +VALUE_PAIR *fr_cursor_next_by_num(vp_cursor_t *cursor, unsigned int attr, unsigned int vendor, int8_t tag) +{ + VALUE_PAIR *i; + + if (!cursor->first) return NULL; + + for (i = !cursor->found ? cursor->current : cursor->found->next; + i != NULL; + i = i->next) { + VERIFY_VP(i); + if ((i->da->attr == attr) && (i->da->vendor == vendor) && + (!i->da->flags.has_tag || TAG_EQ(tag, i->tag))) { + break; + } + } + + return fr_cursor_update(cursor, i); +} + +/** Iterate over attributes of a given DA in the pairlist + * + * Find the next attribute of a given type. If no fr_cursor_next_by_* function + * has been called on a cursor before, or the previous call returned + * NULL, the search will start with the current attribute. Subsequent calls to + * fr_cursor_next_by_* functions will start the search from the previously + * matched attribute. + * + * @note DICT_ATTR pointers are compared, not the attribute numbers and vendors. + * + * @param cursor to operate on. + * @param da to match. + * @param tag to match. Either a tag number or TAG_ANY to match any tagged or + * untagged attribute, TAG_NONE to match attributes without tags. + * @return the next matching VALUE_PAIR, or NULL if no VALUE_PAIRs match. + */ +VALUE_PAIR *fr_cursor_next_by_da(vp_cursor_t *cursor, DICT_ATTR const *da, int8_t tag) +{ + VALUE_PAIR *i; + + if (!cursor->first) return NULL; + + for (i = !cursor->found ? cursor->current : cursor->found->next; + i != NULL; + i = i->next) { + VERIFY_VP(i); + if ((i->da == da) && + (!i->da->flags.has_tag || TAG_EQ(tag, i->tag))) { + break; + } + } + + return fr_cursor_update(cursor, i); +} + +/** Advanced the cursor to the next VALUE_PAIR + * + * @param cursor to operate on. + * @return the next VALUE_PAIR, or NULL if no more VALUE_PAIRS in the collection. + */ +VALUE_PAIR *fr_cursor_next(vp_cursor_t *cursor) +{ + if (!cursor->first) return NULL; + + cursor->current = cursor->next; + if (cursor->current) { + VERIFY_VP(cursor->current); + + /* + * Set this now in case 'current' gets freed before + * fr_cursor_next is called again. + */ + cursor->next = cursor->current->next; + + /* + * Next call to fr_cursor_next_by_num will start from the current + * position in the list, not the last found instance. + */ + cursor->found = NULL; + } + + return cursor->current; +} + +/** Return the next VALUE_PAIR without advancing the cursor + * + * @param cursor to operate on. + * @return the next VALUE_PAIR, or NULL if no more VALUE_PAIRS in the collection. + */ +VALUE_PAIR *fr_cursor_next_peek(vp_cursor_t *cursor) +{ + return cursor->next; +} + +/** Return the VALUE_PAIR the cursor current points to + * + * @param cursor to operate on. + * @return the VALUE_PAIR the cursor currently points to. + */ +VALUE_PAIR *fr_cursor_current(vp_cursor_t *cursor) +{ + if (cursor->current) VERIFY_VP(cursor->current); + + return cursor->current; +} + +/** Insert a single VALUE_PAIR at the end of the list + * + * @note Will not advance cursor position to new attribute, but will set cursor + * to this attribute, if it's the first one in the list. + * + * Insert a VALUE_PAIR at the end of the list. + * + * @param cursor to operate on. + * @param vp to insert. + */ +void fr_cursor_insert(vp_cursor_t *cursor, VALUE_PAIR *vp) +{ + VALUE_PAIR *i; + + if (!fr_assert(cursor->first)) return; /* cursor must have been initialised */ + + if (!vp) return; + + VERIFY_VP(vp); + + /* + * Only allow one VP to by inserted at a time + */ + vp->next = NULL; + + /* + * Cursor was initialised with a pointer to a NULL value_pair + */ + if (!*cursor->first) { + *cursor->first = vp; + cursor->current = vp; + + return; + } + + /* + * We don't yet know where the last VALUE_PAIR is + * + * Assume current is closer to the end of the list and + * use that if available. + */ + if (!cursor->last) cursor->last = cursor->current ? cursor->current : *cursor->first; + + VERIFY_VP(cursor->last); + + /* + * Wind last to the end of the list. + */ + if (cursor->last->next) { + for (i = cursor->last; i; i = i->next) { + VERIFY_VP(i); + cursor->last = i; + } + } + + /* + * Either current was never set, or something iterated to the + * end of the attribute list. In both cases the newly inserted + * VALUE_PAIR should be set as the current VALUE_PAIR. + */ + if (!cursor->current) cursor->current = vp; + + /* + * Add the VALUE_PAIR to the end of the list + */ + cursor->last->next = vp; + cursor->last = vp; /* Wind it forward a little more */ + + /* + * If the next pointer was NULL, and the VALUE_PAIR + * just added has a next pointer value, set the cursor's next + * pointer to the VALUE_PAIR's next pointer. + */ + if (!cursor->next) cursor->next = cursor->current->next; +} + +/** Merges multiple VALUE_PAIR into the cursor + * + * Add multiple VALUE_PAIR from add to cursor. + * + * @param cursor to insert VALUE_PAIRs with + * @param add one or more VALUE_PAIRs (may be NULL, which results in noop). + */ +void fr_cursor_merge(vp_cursor_t *cursor, VALUE_PAIR *add) +{ + vp_cursor_t from; + VALUE_PAIR *vp; + + if (!add) return; + + if (!fr_assert(cursor->first)) return; /* cursor must have been initialised */ + + for (vp = fr_cursor_init(&from, &add); + vp; + vp = fr_cursor_next(&from)) { + fr_cursor_insert(cursor, vp); + } +} + +/** Remove the current pair + * + * @todo this is really inefficient and should be fixed... + * + * The current VP will be set to the one before the VP being removed, + * this is so the commonly used check and remove loop (below) works + * as expected. + @code {.c} + for (vp = fr_cursor_init(&cursor, head); + vp; + vp = fr_cursor_next(&cursor) { + if (<condition>) { + vp = fr_cursor_remove(&cursor); + talloc_free(vp); + } + } + @endcode + * + * @param cursor to remove the current pair from. + * @return NULL on error, else the VALUE_PAIR that was just removed. + */ +VALUE_PAIR *fr_cursor_remove(vp_cursor_t *cursor) +{ + VALUE_PAIR *vp, *before; + + if (!fr_assert(cursor->first)) return NULL; /* cursor must have been initialised */ + + vp = cursor->current; + if (!vp) return NULL; + + /* + * Where VP is head of the list + */ + if (*(cursor->first) == vp) { + *(cursor->first) = vp->next; + cursor->current = vp->next; + cursor->next = vp->next ? vp->next->next : NULL; + before = NULL; + goto fixup; + } + + /* + * Where VP is not head of the list + */ + before = *(cursor->first); + if (!before) return NULL; + + /* + * Find the VP immediately preceding the one being removed + */ + while (before->next != vp) before = before->next; + + cursor->next = before->next = vp->next; /* close the gap */ + cursor->current = before; /* current jumps back one, but this is usually desirable */ + +fixup: + vp->next = NULL; /* limit scope of fr_pair_list_free() */ + + /* + * Fixup cursor->found if we removed the VP it was referring to, + * and point to the previous one. + */ + if (vp == cursor->found) cursor->found = before; + + /* + * Fixup cursor->last if we removed the VP it was referring to + */ + if (vp == cursor->last) cursor->last = cursor->current; + return vp; +} + +/** Replace the current pair + * + * @todo this is really inefficient and should be fixed... + * + * @param cursor to replace the current pair in. + * @param new VALUE_PAIR to insert. + * @return NULL on error, else the VALUE_PAIR we just replaced. + */ +VALUE_PAIR *fr_cursor_replace(vp_cursor_t *cursor, VALUE_PAIR *new) +{ + VALUE_PAIR *vp, **last; + + if (!fr_assert(cursor->first)) return NULL; /* cursor must have been initialised */ + + vp = cursor->current; + if (!vp) { + *cursor->first = new; + return NULL; + } + + last = cursor->first; + while (*last != vp) { + last = &(*last)->next; + } + + fr_cursor_next(cursor); /* Advance the cursor past the one were about to replace */ + + *last = new; + new->next = vp->next; + vp->next = NULL; + + return vp; +} diff --git a/src/lib/debug.c b/src/lib/debug.c new file mode 100644 index 0000000..b000903 --- /dev/null +++ b/src/lib/debug.c @@ -0,0 +1,1217 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file debug.c + * @brief Various functions to aid in debugging + * + * @copyright 2013 The FreeRADIUS server project + * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org> + */ +#include <assert.h> +#include <freeradius-devel/libradius.h> +#include <sys/stat.h> +#include <sys/wait.h> + +USES_APPLE_DEPRECATED_API + +#if defined(HAVE_MALLOPT) && defined(HAVE_MALLOC_H) +# include <malloc.h> +#endif + +/* + * runtime backtrace functions are not POSIX but are included in + * glibc, OSX >= 10.5 and various BSDs + */ +#ifdef HAVE_EXECINFO +# include <execinfo.h> +#endif + +#ifdef HAVE_SYS_PRCTL_H +# include <sys/prctl.h> +#endif + +#ifdef HAVE_SYS_PROCCTL_H +# include <sys/procctl.h> +#endif + +#ifdef HAVE_SYS_PTRACE_H +# include <sys/ptrace.h> +# if !defined(PT_ATTACH) && defined(PTRACE_ATTACH) +# define PT_ATTACH PTRACE_ATTACH +# endif +# if !defined(PT_DETACH) && defined(PTRACE_DETACH) +# define PT_DETACH PTRACE_DETACH +# endif +#endif + +#ifdef HAVE_SYS_RESOURCE_H +# include <sys/resource.h> +#endif + +#ifdef HAVE_PTHREAD_H +# define PTHREAD_MUTEX_LOCK pthread_mutex_lock +# define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock +#else +# define PTHREAD_MUTEX_LOCK(_x) +# define PTHREAD_MUTEX_UNLOCK(_x) +#endif + +#ifdef HAVE_EXECINFO +# ifndef MAX_BT_FRAMES +# define MAX_BT_FRAMES 128 +# endif +# ifndef MAX_BT_CBUFF +# define MAX_BT_CBUFF 1048576 //!< Should be a power of 2 +# endif + +# ifdef HAVE_PTHREAD_H +static pthread_mutex_t fr_debug_init = PTHREAD_MUTEX_INITIALIZER; +# endif + +typedef struct fr_bt_info { + void *obj; //!< Memory address of the block of allocated memory. + void *frames[MAX_BT_FRAMES]; //!< Backtrace frame data + int count; //!< Number of frames stored +} fr_bt_info_t; + +struct fr_bt_marker { + void *obj; //!< Pointer to the parent object, this is our needle + //!< when we iterate over the contents of the circular buffer. + fr_cbuff_t *cbuff; //!< Where we temporarily store the backtraces +}; +#endif + +static char panic_action[512]; //!< The command to execute when panicking. +static fr_fault_cb_t panic_cb = NULL; //!< Callback to execute whilst panicking, before the + //!< panic_action. + +static bool dump_core; //!< Whether we should drop a core on fatal signals. + +static int fr_fault_log_fd = STDERR_FILENO; //!< Where to write debug output. + +fr_debug_state_t fr_debug_state = DEBUG_STATE_UNKNOWN; //!< Whether we're attached to by a debugger. + +#ifdef HAVE_SYS_RESOURCE_H +static struct rlimit core_limits; +#endif + +static TALLOC_CTX *talloc_null_ctx; +static TALLOC_CTX *talloc_autofree_ctx; + +/* + * On BSD systems, ptrace(PT_DETACH) uses a third argument for + * resume address, with the magic value (void *)1 to resume where + * process stopped. Specifying NULL there leads to a crash because + * process resumes at address 0. + */ +#ifdef HAVE_SYS_PTRACE_H +# ifdef __linux__ +# define _PTRACE(_x, _y) ptrace(_x, _y, NULL, NULL) +# define _PTRACE_DETACH(_x) ptrace(PT_DETACH, _x, NULL, NULL) +# else +# define _PTRACE(_x, _y) ptrace(_x, _y, NULL, 0) +# define _PTRACE_DETACH(_x) ptrace(PT_DETACH, _x, (void *)1, 0) +# endif + +# ifdef HAVE_CAPABILITY_H +# include <sys/capability.h> +# endif + +/** Determine if we're running under a debugger by attempting to attach using pattach + * + * @return 0 if we're not, 1 if we are, -1 if we can't tell because of an error, + * -2 if we can't tell because we don't have the CAP_SYS_PTRACE capability. + */ +static int fr_get_debug_state(void) +{ + int pid; + + int from_child[2] = {-1, -1}; + +#ifdef HAVE_CAPABILITY_H + cap_flag_value_t value; + cap_t current; + + /* + * If we're running under linux, we first need to check if we have + * permission to to ptrace. We do that using the capabilities + * functions. + */ + current = cap_get_proc(); + if (!current) { + fr_strerror_printf("Failed getting process capabilities: %s", fr_syserror(errno)); + return DEBUG_STATE_UNKNOWN; + } + + if (cap_get_flag(current, CAP_SYS_PTRACE, CAP_PERMITTED, &value) < 0) { + fr_strerror_printf("Failed getting permitted ptrace capability state: %s", + fr_syserror(errno)); + cap_free(current); + return DEBUG_STATE_UNKNOWN; + } + + if ((value == CAP_SET) && (cap_get_flag(current, CAP_SYS_PTRACE, CAP_EFFECTIVE, &value) < 0)) { + fr_strerror_printf("Failed getting effective ptrace capability state: %s", + fr_syserror(errno)); + cap_free(current); + return DEBUG_STATE_UNKNOWN; + } + + /* + * We don't have permission to ptrace, so this test will always fail. + */ + if (value == CAP_CLEAR) { + fr_strerror_printf("ptrace capability not set. If debugger detection is required run as root or: " + "setcap cap_sys_ptrace+ep <path_to_radiusd>"); + cap_free(current); + return DEBUG_STATE_UNKNOWN_NO_PTRACE_CAP; + } + cap_free(current); +#endif + + if (pipe(from_child) < 0) { + fr_strerror_printf("Error opening internal pipe: %s", fr_syserror(errno)); + return DEBUG_STATE_UNKNOWN; + } + + pid = fork(); + if (pid == -1) { + fr_strerror_printf("Error forking: %s", fr_syserror(errno)); + return DEBUG_STATE_UNKNOWN; + } + + /* Child */ + if (pid == 0) { + int8_t ret = DEBUG_STATE_NOT_ATTACHED; + int ppid = getppid(); + + /* Close parent's side */ + close(from_child[0]); + + /* + * FreeBSD is extremely picky about the order of operations here + * we need to attach, wait *then* write whilst the parent is still + * suspended, then detach, continuing the process. + * + * If we don't do it in that order the read in the parent triggers + * a SIGKILL. + */ + if (_PTRACE(PT_ATTACH, ppid) == 0) { + /* Wait for the parent to stop */ + waitpid(ppid, NULL, 0); + + /* Tell the parent what happened */ + if (write(from_child[1], &ret, sizeof(ret)) < 0) { + fprintf(stderr, "Writing ptrace status to parent failed: %s", fr_syserror(errno)); + } + + /* Detach */ + _PTRACE_DETACH(ppid); + exit(0); + } + + ret = DEBUG_STATE_ATTACHED; + /* Tell the parent what happened */ + if (write(from_child[1], &ret, sizeof(ret)) < 0) { + fprintf(stderr, "Writing ptrace status to parent failed: %s", fr_syserror(errno)); + } + + exit(0); + /* Parent */ + } else { + int8_t ret = DEBUG_STATE_UNKNOWN; + + /* + * The child writes errno (reason) if pattach failed else 0. + * + * This read may be interrupted by pattach, + * which is why we need the loop. + */ + while ((read(from_child[0], &ret, sizeof(ret)) < 0) && (errno == EINTR)); + + /* Close the pipes here (if we did it above, it might race with pattach) */ + close(from_child[1]); + close(from_child[0]); + + /* Collect the status of the child */ + waitpid(pid, NULL, 0); + + return ret; + } +} +#elif defined(HAVE_SYS_PROCCTL_H) +static int fr_get_debug_state(void) +{ + int status; + + if (procctl(P_PID, getpid(), PROC_TRACE_STATUS, &status) == -1) { + fr_strerror_printf("Cannot get dumpable flag: procctl(PROC_TRACE_STATUS) failed: %s", fr_syserror(errno)); + return DEBUG_STATE_UNKNOWN; + } + + /* + * As FreeBSD docs say about "PROC_TRACE_STATUS": + * + * Returns the current tracing status for the specified process in the + * integer variable pointed to by data. If tracing is disabled, data + * is set to -1. If tracing is enabled, but no debugger is attached by + * the ptrace(2) syscall, data is set to 0. If a debugger is attached, + * data is set to the pid of the debugger process. + */ + if (status <= 0) return DEBUG_STATE_NOT_ATTACHED; + + return DEBUG_STATE_ATTACHED; +} +#else +static int fr_get_debug_state(void) +{ + fr_strerror_printf("PTRACE not available"); + + return DEBUG_STATE_UNKNOWN_NO_PTRACE; +} +#endif + +/** Should be run before using setuid or setgid to get useful results + * + * @note sets the fr_debug_state global. + */ +void fr_store_debug_state(void) +{ + fr_debug_state = fr_get_debug_state(); + +#ifndef NDEBUG + /* + * There are many reasons why this might happen with + * a vanilla install, so we don't want to spam users + * with messages they won't understand and may not + * want to resolve. + */ + if (fr_debug_state < 0) fprintf(stderr, "Getting debug state failed: %s\n", fr_strerror()); +#endif +} + +/** Return current value of debug_state + * + * @param state to translate into a humanly readable value. + * @return humanly readable version of debug state. + */ +char const *fr_debug_state_to_msg(fr_debug_state_t state) +{ + switch (state) { + case DEBUG_STATE_UNKNOWN_NO_PTRACE: + return "Debug state unknown (ptrace functionality not available)"; + + case DEBUG_STATE_UNKNOWN_NO_PTRACE_CAP: + return "Debug state unknown (cap_sys_ptrace capability not set)"; + + case DEBUG_STATE_UNKNOWN: + return "Debug state unknown"; + + case DEBUG_STATE_ATTACHED: + return "Found debugger attached"; + + case DEBUG_STATE_NOT_ATTACHED: + return "Debugger not attached"; + } + + return "<INVALID>"; +} + +/** Break in debugger (if were running under a debugger) + * + * If the server is running under a debugger this will raise a + * SIGTRAP which will pause the running process. + * + * If the server is not running under debugger then this will do nothing. + */ +void fr_debug_break(bool always) +{ + if (always) raise(SIGTRAP); + + if (fr_debug_state < 0) fr_debug_state = fr_get_debug_state(); + if (fr_debug_state == DEBUG_STATE_ATTACHED) { + fprintf(stderr, "Debugger detected, raising SIGTRAP\n"); + fflush(stderr); + + raise(SIGTRAP); + } +} + +#ifdef HAVE_EXECINFO +/** Print backtrace entry for a given object + * + * @param cbuff to search in. + * @param obj pointer to original object + */ +void backtrace_print(fr_cbuff_t *cbuff, void *obj) +{ + fr_bt_info_t *p; + bool found = false; + + while ((p = fr_cbuff_rp_next(cbuff, NULL))) { + if ((p->obj == obj) || !obj) { + found = true; + + fprintf(stderr, "Stacktrace for: %p\n", p->obj); + backtrace_symbols_fd(p->frames, p->count, STDERR_FILENO); + } + }; + + if (!found) { + fprintf(stderr, "No backtrace available for %p", obj); + } +} + +/** Generate a backtrace for an object + * + * If this is the first entry being inserted + */ +int fr_backtrace_do(fr_bt_marker_t *marker) +{ + fr_bt_info_t *bt; + + if (!fr_assert(marker->obj) || !fr_assert(marker->cbuff)) return -1; + + bt = talloc_zero(NULL, fr_bt_info_t); + if (!bt) return -1; + + bt->obj = marker->obj; + bt->count = backtrace(bt->frames, MAX_BT_FRAMES); + + fr_cbuff_rp_insert(marker->cbuff, bt); + + return 0; +} + +/** Inserts a backtrace marker into the provided context + * + * Allows for maximum laziness and will initialise a circular buffer if one has not already been created. + * + * Code augmentation should look something like: +@verbatim + // Create a static cbuffer pointer, the first call to backtrace_attach will initialise it + static fr_cbuff_t *my_obj_bt; + + my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) { + my_obj_t *this; + + this = talloc(ctx, my_obj_t); + + // Attach backtrace marker to object + backtrace_attach(&my_obj_bt, this); + + return this; + } +@endverbatim + * + * Then, later when a double free occurs: +@verbatim + (gdb) call backtrace_print(&my_obj_bt, <pointer to double freed memory>) +@endverbatim + * + * which should print a limited backtrace to stderr. Note, this backtrace will not include any argument + * values, but should at least show the code path taken. + * + * @param cbuff this should be a pointer to a static *fr_cbuff. + * @param obj we want to generate a backtrace for. + */ +fr_bt_marker_t *fr_backtrace_attach(fr_cbuff_t **cbuff, TALLOC_CTX *obj) +{ + fr_bt_marker_t *marker; + + if (*cbuff == NULL) { + PTHREAD_MUTEX_LOCK(&fr_debug_init); + /* Check again now we hold the mutex - eww*/ + if (*cbuff == NULL) *cbuff = fr_cbuff_alloc(NULL, MAX_BT_CBUFF, true); + PTHREAD_MUTEX_UNLOCK(&fr_debug_init); + } + + marker = talloc(obj, fr_bt_marker_t); + if (!marker) { + return NULL; + } + + marker->obj = (void *) obj; + marker->cbuff = *cbuff; + + fprintf(stderr, "Backtrace attached to %s %p\n", talloc_get_name(obj), obj); + /* + * Generate the backtrace for memory allocation + */ + fr_backtrace_do(marker); + talloc_set_destructor(marker, fr_backtrace_do); + + return marker; +} +#else +void backtrace_print(UNUSED fr_cbuff_t *cbuff, UNUSED void *obj) +{ + fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n"); +} +fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_cbuff_t **cbuff, UNUSED TALLOC_CTX *obj) +{ + fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n"); + abort(); +} +#endif /* ifdef HAVE_EXECINFO */ + +static int _panic_on_free(UNUSED char *foo) +{ + fr_fault(SIGABRT); + return -1; /* this should make the free fail */ +} + +/** Insert memory into the context of another talloc memory chunk which + * causes a panic when freed. + * + * @param ctx TALLOC_CTX to monitor for frees. + */ +void fr_panic_on_free(TALLOC_CTX *ctx) +{ + char *ptr; + + ptr = talloc(ctx, char); + talloc_set_destructor(ptr, _panic_on_free); +} + +/** Set the dumpable flag, also controls whether processes can PATTACH + * + * @param dumpable whether we should allow core dumping + */ +#if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE) +static int fr_set_dumpable_flag(bool dumpable) +{ + if (prctl(PR_SET_DUMPABLE, dumpable ? 1 : 0) < 0) { + fr_strerror_printf("Cannot re-enable core dumps: prctl(PR_SET_DUMPABLE) failed: %s", + fr_syserror(errno)); + return -1; + } + + return 0; +} +#elif defined(HAVE_SYS_PROCCTL_H) +static int fr_set_dumpable_flag(bool dumpable) +{ + int mode = dumpable ? PROC_TRACE_CTL_ENABLE : PROC_TRACE_CTL_DISABLE; + + if (procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode) == -1) { + fr_strerror_printf("Cannot re-enable core dumps: procctl(PROC_TRACE_CTL) failed: %s", + fr_syserror(errno)); + return -1; + } + + return 0; +} +#else +static int fr_set_dumpable_flag(UNUSED bool dumpable) +{ + fr_strerror_printf("Changing value of PR_DUMPABLE not supported on this system"); + return -2; +} +#endif + +/** Get the processes dumpable flag + * + */ +#if defined(HAVE_SYS_PRCTL_H) && defined(PR_GET_DUMPABLE) +static int fr_get_dumpable_flag(void) +{ + int ret; + + ret = prctl(PR_GET_DUMPABLE); + if (ret < 0) { + fr_strerror_printf("Cannot get dumpable flag: %s", fr_syserror(errno)); + return -1; + } + + /* + * Linux is crazy and prctl sometimes returns 2 for disabled + */ + if (ret != 1) return 0; + return 1; +} +#elif defined(HAVE_SYS_PROCCTL_H) +static int fr_get_dumpable_flag(void) +{ + int status; + + if (procctl(P_PID, getpid(), PROC_TRACE_CTL, &status) == -1) { + fr_strerror_printf("Cannot get dumpable flag: procctl(PROC_TRACE_CTL) failed: %s", fr_syserror(errno)); + return -1; + } + + /* + * There are a few different kinds of disabled, but only + * one ENABLE. + */ + if (status != PROC_TRACE_CTL_ENABLE) return 0; + + return 1; +} +#else +static int fr_get_dumpable_flag(void) +{ + fr_strerror_printf("Getting value of PR_DUMPABLE not supported on this system"); + return -2; +} +#endif + + +/** Get the current maximum for core files + * + * Do this before anything else so as to ensure it's properly initialized. + */ +int fr_set_dumpable_init(void) +{ +#ifdef HAVE_SYS_RESOURCE_H + if (getrlimit(RLIMIT_CORE, &core_limits) < 0) { + fr_strerror_printf("Failed to get current core limit: %s", fr_syserror(errno)); + return -1; + } +#endif + return 0; +} + +/** Enable or disable core dumps + * + * @param allow_core_dumps whether to enable or disable core dumps. + */ +int fr_set_dumpable(bool allow_core_dumps) +{ + dump_core = allow_core_dumps; + /* + * If configured, turn core dumps off. + */ + if (!allow_core_dumps) { +#ifdef HAVE_SYS_RESOURCE_H + struct rlimit no_core; + + no_core.rlim_cur = 0; + no_core.rlim_max = core_limits.rlim_max; + + if (setrlimit(RLIMIT_CORE, &no_core) < 0) { + fr_strerror_printf("Failed disabling core dumps: %s", fr_syserror(errno)); + + return -1; + } +#endif + return 0; + } + + if (fr_set_dumpable_flag(true) < 0) return -1; + + /* + * Reset the core dump limits to their original value. + */ +#ifdef HAVE_SYS_RESOURCE_H + if (setrlimit(RLIMIT_CORE, &core_limits) < 0) { + fr_strerror_printf("Cannot update core dump limit: %s", fr_syserror(errno)); + + return -1; + } +#endif + return 0; +} + +/** Reset dumpable state to previously configured value + * + * Needed after suid up/down + * + * @return 0 on success, else -1 on failure. + */ +int fr_reset_dumpable(void) +{ + return fr_set_dumpable(dump_core); +} + +/** Check to see if panic_action file is world writeable + * + * @return 0 if file is OK, else -1. + */ +static int fr_fault_check_permissions(void) +{ + char const *p, *q; + size_t len; + char filename[256]; + struct stat statbuf; + + /* + * Try and guess which part of the command is the binary, and check to see if + * it's world writeable, to try and save the admin from their own stupidity. + * + * @fixme we should do this properly and take into account single and double + * quotes. + */ + if ((q = strchr(panic_action, ' '))) { + /* + * need to use a static buffer, because mallocing memory in a signal handler + * is a bad idea and can result in deadlock. + */ + len = snprintf(filename, sizeof(filename), "%.*s", (int)(q - panic_action), panic_action); + if (is_truncated(len, sizeof(filename))) { + fr_strerror_printf("Failed writing panic_action to temporary buffer (truncated)"); + return -1; + } + p = filename; + } else { + p = panic_action; + } + + if (stat(p, &statbuf) == 0) { +#ifdef S_IWOTH + if ((statbuf.st_mode & S_IWOTH) != 0) { + fr_strerror_printf("panic_action file \"%s\" is globally writable", p); + return -1; + } +#endif + } + + return 0; +} + +/** Prints a simple backtrace (if execinfo is available) and calls panic_action if set. + * + * @param sig caught + */ +NEVER_RETURNS void fr_fault(int sig) +{ + char cmd[sizeof(panic_action) + 20]; + char *out = cmd; + size_t left = sizeof(cmd), ret; + + char const *p = panic_action; + char const *q; + + int code; + + /* + * If a debugger is attached, we don't want to run the panic action, + * as it may interfere with the operation of the debugger. + * If something calls us directly we just raise the signal and let + * the debugger handle it how it wants. + */ + if (fr_debug_state == DEBUG_STATE_ATTACHED) { + FR_FAULT_LOG("RAISING SIGNAL: %s", strsignal(sig)); + raise(sig); + goto finish; + } + + /* + * Makes the backtraces slightly cleaner + */ + memset(cmd, 0, sizeof(cmd)); + + FR_FAULT_LOG("CAUGHT SIGNAL: %s", strsignal(sig)); + + /* + * Check for administrator sanity. + */ + if (fr_fault_check_permissions() < 0) { + FR_FAULT_LOG("Refusing to execute panic action: %s", fr_strerror()); + goto finish; + } + + /* + * Run the callback if one was registered + */ + if (panic_cb && (panic_cb(sig) < 0)) goto finish; + + /* + * Produce a simple backtrace - They're very basic but at least give us an + * idea of the area of the code we hit the issue in. + * + * See below in fr_fault_setup() and + * https://sourceware.org/bugzilla/show_bug.cgi?id=16159 + * for why we only print backtraces in debug builds if we're using GLIBC. + */ +#if defined(HAVE_EXECINFO) && (!defined(NDEBUG) || !defined(__GNUC__)) + if (fr_fault_log_fd >= 0) { + size_t frame_count; + void *stack[MAX_BT_FRAMES]; + + frame_count = backtrace(stack, MAX_BT_FRAMES); + + FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count); + + backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd); + } +#endif + + /* No panic action set... */ + if (panic_action[0] == '\0') { + FR_FAULT_LOG("No panic action set"); + goto finish; + } + + /* Substitute %p for the current PID (useful for attaching a debugger) */ + while ((q = strstr(p, "%p"))) { + out += ret = snprintf(out, left, "%.*s%d", (int) (q - p), p, (int) getpid()); + if (left <= ret) { + oob: + FR_FAULT_LOG("Panic action too long"); + fr_exit_now(1); + } + left -= ret; + p = q + 2; + } + if (strlen(p) >= left) goto oob; + strlcpy(out, p, left); + + { + bool disable = false; + + FR_FAULT_LOG("Calling: %s", cmd); + + /* + * Here we temporarily enable the dumpable flag so if GBD or LLDB + * is called in the panic_action, they can pattach to the running + * process. + */ + if (fr_get_dumpable_flag() == 0) { + if ((fr_set_dumpable_flag(true) < 0) || !fr_get_dumpable_flag()) { + FR_FAULT_LOG("Failed setting dumpable flag, pattach may not work: %s", fr_strerror()); + } else { + disable = true; + } + FR_FAULT_LOG("Temporarily setting PR_DUMPABLE to 1"); + } + + code = system(cmd); + + /* + * We only want to error out here, if dumpable was originally disabled + * and we managed to change the value to enabled, but failed + * setting it back to disabled. + */ + if (disable) { + FR_FAULT_LOG("Resetting PR_DUMPABLE to 0"); + if (fr_set_dumpable_flag(false) < 0) { + FR_FAULT_LOG("Failed resetting dumpable flag to off: %s", fr_strerror()); + FR_FAULT_LOG("Exiting due to insecure process state"); + fr_exit_now(1); + } + } + + FR_FAULT_LOG("Panic action exited with %i", code); + + fr_exit_now(code); + } + + +finish: + /* + * (Re-)Raise the signal, so that if we're running under + * a debugger, the debugger can break when it receives + * the signal. + */ + fr_unset_signal(sig); /* Make sure we don't get into a loop */ + + raise(sig); + + fr_exit_now(1); /* Function marked as noreturn */ +} + +/** Callback executed on fatal talloc error + * + * This is the simple version which mostly behaves the same way as the default + * one, and will not call panic_action. + * + * @param reason string provided by talloc. + */ +static void _fr_talloc_fault_simple(char const *reason) CC_HINT(noreturn); +static void _fr_talloc_fault_simple(char const *reason) +{ + FR_FAULT_LOG("talloc abort: %s\n", reason); + +#if defined(HAVE_EXECINFO) && (!defined(NDEBUG) || !defined(__GNUC__)) + if (fr_fault_log_fd >= 0) { + size_t frame_count; + void *stack[MAX_BT_FRAMES]; + + frame_count = backtrace(stack, MAX_BT_FRAMES); + FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count); + backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd); + } +#endif + abort(); +} + +/** Callback executed on fatal talloc error + * + * Translates a talloc abort into a fr_fault call. + * Mostly to work around issues with some debuggers not being able to + * attach after a SIGABRT has been raised. + * + * @param reason string provided by talloc. + */ +static void _fr_talloc_fault(char const *reason) CC_HINT(noreturn); +static void _fr_talloc_fault(char const *reason) +{ + FR_FAULT_LOG("talloc abort: %s", reason); +#ifdef SIGABRT + fr_fault(SIGABRT); +#endif + fr_exit_now(1); +} + +/** Wrapper to pass talloc log output to our fr_fault_log function + * + */ +static void _fr_talloc_log(char const *msg) +{ + fr_fault_log("%s\n", msg); +} + +/** Generate a talloc memory report for a context and print to stderr/stdout + * + * @param ctx to generate a report for, may be NULL in which case the root context is used. + */ +int fr_log_talloc_report(TALLOC_CTX *ctx) +{ +#define TALLOC_REPORT_MAX_DEPTH 20 + + FILE *log; + int fd; + + fd = dup(fr_fault_log_fd); + if (fd < 0) { + fr_strerror_printf("Couldn't write memory report, failed to dup log fd: %s", fr_syserror(errno)); + return -1; + } + log = fdopen(fd, "w"); + if (!log) { + close(fd); + fr_strerror_printf("Couldn't write memory report, fdopen failed: %s", fr_syserror(errno)); + return -1; + } + + if (!ctx) { + fprintf(log, "Current state of talloced memory:\n"); + talloc_report_full(talloc_null_ctx, log); + } else { + int i; + + fprintf(log, "Talloc chunk lineage:\n"); + fprintf(log, "%p (%s)", ctx, talloc_get_name(ctx)); + + i = 0; + while ((i < TALLOC_REPORT_MAX_DEPTH) && (ctx = talloc_parent(ctx))) { + fprintf(log, " < %p (%s)", ctx, talloc_get_name(ctx)); + i++; + } + fprintf(log, "\n"); + + i = 0; + do { + fprintf(log, "Talloc context level %i:\n", i++); + talloc_report_full(ctx, log); + } while ((ctx = talloc_parent(ctx)) && + (i < TALLOC_REPORT_MAX_DEPTH) && + (talloc_parent(ctx) != talloc_autofree_ctx) && /* Stop before we hit the autofree ctx */ + (talloc_parent(ctx) != talloc_null_ctx)); /* Stop before we hit NULL ctx */ + } + + fclose(log); + + return 0; +} + + +static int _fr_disable_null_tracking(UNUSED bool *p) +{ + talloc_disable_null_tracking(); + return 0; +} + +/** Register talloc fault handlers + * + * Just register the fault handlers we need to make talloc + * produce useful debugging output. + */ +void fr_talloc_fault_setup(void) +{ + talloc_set_log_fn(_fr_talloc_log); + talloc_set_abort_fn(_fr_talloc_fault_simple); +} + +/** Registers signal handlers to execute panic_action on fatal signal + * + * May be called multiple time to change the panic_action/program. + * + * @param cmd to execute on fault. If present %p will be substituted + * for the parent PID before the command is executed, and %e + * will be substituted for the currently running program. + * @param program Name of program currently executing (argv[0]). + * @return 0 on success -1 on failure. + */ +DIAG_OFF(deprecated-declarations) +int fr_fault_setup(char const *cmd, char const *program) +{ + static bool setup = false; + + char *out = panic_action; + size_t left = sizeof(panic_action); + + char const *p = cmd; + char const *q; + + if (cmd) { + size_t ret; + + /* Substitute %e for the current program */ + while ((q = strstr(p, "%e"))) { + out += ret = snprintf(out, left, "%.*s%s", (int) (q - p), p, program ? program : ""); + if (left <= ret) { + oob: + fr_strerror_printf("Panic action too long"); + return -1; + } + left -= ret; + p = q + 2; + } + if (strlen(p) >= left) goto oob; + strlcpy(out, p, left); + } else { + *panic_action = '\0'; + } + + /* + * Check for administrator sanity. + */ + if (fr_fault_check_permissions() < 0) return -1; + + /* Unsure what the side effects of changing the signal handler mid execution might be */ + if (!setup) { + char *env; + fr_debug_state_t debug_state; + + /* + * Installing signal handlers interferes with some debugging + * operations. Give the developer control over whether the + * signal handlers are installed or not. + */ + env = getenv("DEBUG"); + if (!env || (strcmp(env, "no") == 0)) { + debug_state = DEBUG_STATE_NOT_ATTACHED; + } else if (!strcmp(env, "auto") || !strcmp(env, "yes")) { + /* + * Figure out if we were started under a debugger + */ + if (fr_debug_state < 0) fr_debug_state = fr_get_debug_state(); + debug_state = fr_debug_state; + } else { + debug_state = DEBUG_STATE_ATTACHED; + } + + talloc_set_log_fn(_fr_talloc_log); + + /* + * These signals can't be properly dealt with in the debugger + * if we set our own signal handlers. + */ + switch (debug_state) { + default: +#ifndef NDEBUG + FR_FAULT_LOG("Debugger check failed: %s", fr_strerror()); + FR_FAULT_LOG("Signal processing in debuggers may not work as expected"); +#endif + /* FALL-THROUGH */ + + case DEBUG_STATE_NOT_ATTACHED: +#ifdef SIGABRT + if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1; + + /* + * Use this instead of abort so we get a + * full backtrace with broken versions of LLDB + */ + talloc_set_abort_fn(_fr_talloc_fault); +#endif +#ifdef SIGILL + if (fr_set_signal(SIGILL, fr_fault) < 0) return -1; +#endif +#ifdef SIGFPE + if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1; +#endif +#ifdef SIGSEGV + if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1; +#endif + break; + + case DEBUG_STATE_ATTACHED: + break; + } + + /* + * Needed for memory reports + */ + { + TALLOC_CTX *tmp; + bool *marker; + + tmp = talloc(NULL, bool); + talloc_null_ctx = talloc_parent(tmp); + talloc_free(tmp); + + /* + * Disable null tracking on exit, else valgrind complains + */ + talloc_autofree_ctx = talloc_autofree_context(); + marker = talloc(talloc_autofree_ctx, bool); + talloc_set_destructor(marker, _fr_disable_null_tracking); + } + +#if defined(HAVE_MALLOPT) && !defined(NDEBUG) + /* + * If were using glibc malloc > 2.4 this scribbles over + * uninitialised and freed memory, to make memory issues easier + * to track down. + */ + if (!getenv("TALLOC_FREE_FILL")) mallopt(M_PERTURB, 0x42); + mallopt(M_CHECK_ACTION, 3); +#endif + +#if defined(HAVE_EXECINFO) && defined(__GNUC__) && !defined(NDEBUG) + /* + * We need to pre-load lgcc_s, else we can get into a deadlock + * in fr_fault, as backtrace() attempts to dlopen it. + * + * Apparently there's a performance impact of loading lgcc_s, + * so only do it if this is a debug build. + * + * See: https://sourceware.org/bugzilla/show_bug.cgi?id=16159 + */ + { + void *stack[10]; + + backtrace(stack, 10); + } +#endif + } + setup = true; + + return 0; +} +DIAG_ON(deprecated-declarations) + +/** Set a callback to be called before fr_fault() + * + * @param func to execute. If callback returns < 0 + * fr_fault will exit before running panic_action code. + */ +void fr_fault_set_cb(fr_fault_cb_t func) +{ + panic_cb = func; +} + +/** Log output to the fr_fault_log_fd + * + * We used to support a user defined callback, which was set to a radlog + * function. Unfortunately, when logging to syslog, syslog would malloc memory + * which would result in a deadlock if fr_fault was triggered from within + * a malloc call. + * + * Now we just write directly to the FD. + */ +void fr_fault_log(char const *msg, ...) +{ + va_list ap; + + if (fr_fault_log_fd < 0) return; + + va_start(ap, msg); + vdprintf(fr_fault_log_fd, msg, ap); + va_end(ap); +} + +/** Set a file descriptor to log memory reports to. + * + * @param fd to write output to. + */ +void fr_fault_set_log_fd(int fd) +{ + fr_fault_log_fd = fd; +} + +/** A soft assertion which triggers the fault handler in debug builds + * + * @param file the assertion failed in. + * @param line of the assertion in the file. + * @param expr that was evaluated. + * @param cond Result of evaluating the expression. + * @return the value of cond. + */ +bool fr_assert_cond(char const *file, int line, char const *expr, bool cond) +{ + if (!cond) { + FR_FAULT_LOG("SOFT ASSERT FAILED %s[%u]: %s", file, line, expr); +#if !defined(NDEBUG) + fr_fault(SIGABRT); +#endif + return false; + } + + return cond; +} + +/** Exit possibly printing a message about why we're exiting. + * + * @note Use the fr_exit(status) macro instead of calling this function directly. + * + * @param file where fr_exit() was called. + * @param line where fr_exit() was called. + * @param status we're exiting with. + */ +void NEVER_RETURNS _fr_exit(char const *file, int line, int status) +{ +#ifndef NDEBUG + char const *error = fr_strerror(); + + if (error && *error && (status != 0)) { + FR_FAULT_LOG("EXIT(%i) CALLED %s[%u]. Last error was: %s", status, file, line, error); + } else { + FR_FAULT_LOG("EXIT(%i) CALLED %s[%u]", status, file, line); + } +#endif + fr_debug_break(false); /* If running under GDB we'll break here */ + + exit(status); +} + +/** Exit possibly printing a message about why we're exiting. + * + * @note Use the fr_exit_now(status) macro instead of calling this function directly. + * + * @param file where fr_exit_now() was called. + * @param line where fr_exit_now() was called. + * @param status we're exiting with. + */ +void NEVER_RETURNS _fr_exit_now(char const *file, int line, int status) +{ +#ifndef NDEBUG + char const *error = fr_strerror(); + + if (error && (status != 0)) { + FR_FAULT_LOG("_EXIT(%i) CALLED %s[%u]. Last error was: %s", status, file, line, error); + } else { + FR_FAULT_LOG("_EXIT(%i) CALLED %s[%u]", status, file, line); + } +#endif + fr_debug_break(false); /* If running under GDB we'll break here */ + + _exit(status); +} diff --git a/src/lib/dict.c b/src/lib/dict.c new file mode 100644 index 0000000..d425a67 --- /dev/null +++ b/src/lib/dict.c @@ -0,0 +1,3506 @@ +/* + * dict.c Routines to read the dictionary file. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#ifdef WITH_DHCP +#include <freeradius-devel/dhcp.h> +#endif + +#include <ctype.h> + +#ifdef HAVE_MALLOC_H +#include <malloc.h> +#endif + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +static fr_hash_table_t *vendors_byname = NULL; +static fr_hash_table_t *vendors_byvalue = NULL; + +static fr_hash_table_t *attributes_byname = NULL; +static fr_hash_table_t *attributes_byvalue = NULL; + +static fr_hash_table_t *attributes_combo = NULL; + +static fr_hash_table_t *values_byvalue = NULL; +static fr_hash_table_t *values_byname = NULL; + +static DICT_ATTR *dict_base_attrs[256]; + +/* + * For faster HUP's, we cache the stat information for + * files we've $INCLUDEd + */ +typedef struct dict_stat_t { + struct dict_stat_t *next; + struct stat stat_buf; +} dict_stat_t; + +static dict_stat_t *stat_head = NULL; +static dict_stat_t *stat_tail = NULL; + +typedef struct value_fixup_t { + char attrstr[DICT_ATTR_MAX_NAME_LEN]; + DICT_VALUE *dval; + struct value_fixup_t *next; +} value_fixup_t; + + +/* + * So VALUEs in the dictionary can have forward references. + */ +static value_fixup_t *value_fixup = NULL; + +const FR_NAME_NUMBER dict_attr_types[] = { + { "integer", PW_TYPE_INTEGER }, + { "string", PW_TYPE_STRING }, + { "ipaddr", PW_TYPE_IPV4_ADDR }, + { "date", PW_TYPE_DATE }, + { "abinary", PW_TYPE_ABINARY }, + { "octets", PW_TYPE_OCTETS }, + { "ifid", PW_TYPE_IFID }, + { "ipv6addr", PW_TYPE_IPV6_ADDR }, + { "ipv6prefix", PW_TYPE_IPV6_PREFIX }, + { "byte", PW_TYPE_BYTE }, + { "short", PW_TYPE_SHORT }, + { "ether", PW_TYPE_ETHERNET }, + { "combo-ip", PW_TYPE_COMBO_IP_ADDR }, + { "tlv", PW_TYPE_TLV }, + { "signed", PW_TYPE_SIGNED }, + { "extended", PW_TYPE_EXTENDED }, + { "long-extended", PW_TYPE_LONG_EXTENDED }, + { "evs", PW_TYPE_EVS }, + { "uint8", PW_TYPE_BYTE }, + { "uint16", PW_TYPE_SHORT }, + { "uint32", PW_TYPE_INTEGER }, + { "int32", PW_TYPE_SIGNED }, + { "integer64", PW_TYPE_INTEGER64 }, + { "uint64", PW_TYPE_INTEGER64 }, + { "ipv4prefix", PW_TYPE_IPV4_PREFIX }, + { "cidr", PW_TYPE_IPV4_PREFIX }, + { "vsa", PW_TYPE_VSA }, + { NULL, 0 } +}; + +/* + * Map data types to min / max data sizes. + */ +const size_t dict_attr_sizes[PW_TYPE_MAX][2] = { + [PW_TYPE_INVALID] = {~0, 0}, + [PW_TYPE_STRING] = {0, ~0}, + [PW_TYPE_INTEGER] = {4, 4 }, + [PW_TYPE_IPV4_ADDR] = {4, 4}, + [PW_TYPE_DATE] = {4, 4}, + [PW_TYPE_ABINARY] = {32, ~0}, + [PW_TYPE_OCTETS] = {0, ~0}, + [PW_TYPE_IFID] = {8, 8}, + [PW_TYPE_IPV6_ADDR] = {16, 16}, + [PW_TYPE_IPV6_PREFIX] = {2, 18}, + [PW_TYPE_BYTE] = {1, 1}, + [PW_TYPE_SHORT] = {2, 2}, + [PW_TYPE_ETHERNET] = {6, 6}, + [PW_TYPE_SIGNED] = {4, 4}, + [PW_TYPE_COMBO_IP_ADDR] = {4, 16}, + [PW_TYPE_TLV] = {2, ~0}, + [PW_TYPE_EXTENDED] = {2, ~0}, + [PW_TYPE_LONG_EXTENDED] = {3, ~0}, + [PW_TYPE_EVS] = {6, ~0}, + [PW_TYPE_INTEGER64] = {8, 8}, + [PW_TYPE_IPV4_PREFIX] = {6, 6}, + [PW_TYPE_VSA] = {4, ~0} +}; + +/* + * For packing multiple TLV numbers into one 32-bit integer. The + * first 3 bytes are just the 8-bit number. The next two are + * more limited. We only allow 31 attributes nested 3 layers + * deep, and only 7 nested 4 layers deep. This should be + * sufficient for most purposes. + * + * For TLVs and extended attributes, we packet the base attribute + * number into the upper 8 bits of the "vendor" field. + * + * e.g. OID attribute vendor + * 241.1 1 (241 << 24) + * 241.26.9.1 1 (241 << 24) | (9) + * 241.1.2 1 | (2 << 8) (241 << 24) + */ +#define MAX_TLV_NEST (4) +/* + * Bit packing: + * 8 bits of base attribute + * 8 bits for nested TLV 1 + * 8 bits for nested TLV 2 + * 5 bits for nested TLV 3 + * 3 bits for nested TLV 4 + */ +int const fr_attr_max_tlv = MAX_TLV_NEST; +int const fr_attr_shift[MAX_TLV_NEST + 1] = { 0, 8, 16, 24, 29 }; + +unsigned const fr_attr_mask[MAX_TLV_NEST + 1] = { 0xff, 0xff, 0xff, 0x1f, 0x07 }; + +/* + * attr & fr_attr_parent_mask[i] == Nth parent of attr + */ +static unsigned int const fr_attr_parent_mask[MAX_TLV_NEST + 1] = { 0, 0x000000ff, 0x0000ffff, 0x00ffffff, 0x1fffffff }; + +/* + * Create the hash of the name. + * + * We copy the hash function here because it's substantially faster. + */ +#define FNV_MAGIC_INIT (0x811c9dc5) +#define FNV_MAGIC_PRIME (0x01000193) + +static uint32_t dict_hashname(char const *name) +{ + uint32_t hash = FNV_MAGIC_INIT; + char const *p; + + for (p = name; *p != '\0'; p++) { + int c = *(unsigned char const *) p; + if (isalpha(c)) c = tolower(c); + + hash *= FNV_MAGIC_PRIME; + hash ^= (uint32_t ) (c & 0xff); + } + + return hash; +} + + +/* + * Hash callback functions. + */ +static uint32_t dict_attr_name_hash(void const *data) +{ + return dict_hashname(((DICT_ATTR const *)data)->name); +} + +static int dict_attr_name_cmp(void const *one, void const *two) +{ + DICT_ATTR const *a = one; + DICT_ATTR const *b = two; + + return strcasecmp(a->name, b->name); +} + +static uint32_t dict_attr_value_hash(void const *data) +{ + uint32_t hash; + DICT_ATTR const *attr = data; + + hash = fr_hash(&attr->vendor, sizeof(attr->vendor)); + return fr_hash_update(&attr->attr, sizeof(attr->attr), hash); +} + +static int dict_attr_value_cmp(void const *one, void const *two) +{ + DICT_ATTR const *a = one; + DICT_ATTR const *b = two; + + if (a->vendor < b->vendor) return -1; + if (a->vendor > b->vendor) return +1; + + return a->attr - b->attr; +} + +static uint32_t dict_attr_combo_hash(void const *data) +{ + uint32_t hash; + DICT_ATTR const *attr = data; + + hash = fr_hash(&attr->vendor, sizeof(attr->vendor)); + hash = fr_hash_update(&attr->type, sizeof(attr->type), hash); + return fr_hash_update(&attr->attr, sizeof(attr->attr), hash); +} + +static int dict_attr_combo_cmp(void const *one, void const *two) +{ + DICT_ATTR const *a = one; + DICT_ATTR const *b = two; + + if (a->type < b->type) return -1; + if (a->type > b->type) return +1; + + if (a->vendor < b->vendor) return -1; + if (a->vendor > b->vendor) return +1; + + return a->attr - b->attr; +} + +static uint32_t dict_vendor_name_hash(void const *data) +{ + return dict_hashname(((DICT_VENDOR const *)data)->name); +} + +static int dict_vendor_name_cmp(void const *one, void const *two) +{ + DICT_VENDOR const *a = one; + DICT_VENDOR const *b = two; + + return strcasecmp(a->name, b->name); +} + +static uint32_t dict_vendor_value_hash(void const *data) +{ + return fr_hash(&(((DICT_VENDOR const *)data)->vendorpec), + sizeof(((DICT_VENDOR const *)data)->vendorpec)); +} + +static int dict_vendor_value_cmp(void const *one, void const *two) +{ + DICT_VENDOR const *a = one; + DICT_VENDOR const *b = two; + + return a->vendorpec - b->vendorpec; +} + +static uint32_t dict_value_name_hash(void const *data) +{ + uint32_t hash; + DICT_VALUE const *dval = data; + + hash = dict_hashname(dval->name); + hash = fr_hash_update(&dval->vendor, sizeof(dval->vendor), hash); + return fr_hash_update(&dval->attr, sizeof(dval->attr), hash); +} + +static int dict_value_name_cmp(void const *one, void const *two) +{ + int rcode; + DICT_VALUE const *a = one; + DICT_VALUE const *b = two; + + rcode = a->attr - b->attr; + if (rcode != 0) return rcode; + + rcode = a->vendor - b->vendor; + if (rcode != 0) return rcode; + + return strcasecmp(a->name, b->name); +} + +static uint32_t dict_value_value_hash(void const *data) +{ + uint32_t hash; + DICT_VALUE const *dval = data; + + hash = fr_hash(&dval->attr, sizeof(dval->attr)); + hash = fr_hash_update(&dval->vendor, sizeof(dval->vendor), hash); + return fr_hash_update(&dval->value, sizeof(dval->value), hash); +} + +static int dict_value_value_cmp(void const *one, void const *two) +{ + int rcode; + DICT_VALUE const *a = one; + DICT_VALUE const *b = two; + + if (a->vendor < b->vendor) return -1; + if (a->vendor > b->vendor) return +1; + + rcode = a->attr - b->attr; + if (rcode != 0) return rcode; + + return a->value - b->value; +} + + +/* + * Free the list of stat buffers + */ +static void dict_stat_free(void) +{ + dict_stat_t *this, *next; + + if (!stat_head) { + stat_tail = NULL; + return; + } + + for (this = stat_head; this != NULL; this = next) { + next = this->next; + free(this); + } + + stat_head = stat_tail = NULL; +} + + +/* + * Add an entry to the list of stat buffers. + */ +static void dict_stat_add(struct stat const *stat_buf) +{ + dict_stat_t *this; + + this = malloc(sizeof(*this)); + if (!this) return; + memset(this, 0, sizeof(*this)); + + memcpy(&(this->stat_buf), stat_buf, sizeof(this->stat_buf)); + + if (!stat_head) { + stat_head = stat_tail = this; + } else { + stat_tail->next = this; + stat_tail = this; + } +} + + +/* + * See if any dictionaries have changed. If not, don't + * do anything. + */ +static int dict_stat_check(char const *dir, char const *file) +{ + struct stat stat_buf; + dict_stat_t *this; + char buffer[2048]; + + /* + * Nothing cached, all files are new. + */ + if (!stat_head) return 0; + + /* + * Stat the file. + */ + snprintf(buffer, sizeof(buffer), "%s/%s", dir, file); + if (stat(buffer, &stat_buf) < 0) return 0; + + /* + * Find the cache entry. + * FIXME: use a hash table. + * FIXME: check dependencies, via children. + * if A loads B and B changes, we probably want + * to reload B at the minimum. + */ + for (this = stat_head; this != NULL; this = this->next) { + if (this->stat_buf.st_dev != stat_buf.st_dev) continue; + if (this->stat_buf.st_ino != stat_buf.st_ino) continue; + + /* + * The file has changed. Re-read it. + */ + if (this->stat_buf.st_mtime < stat_buf.st_mtime) return 0; + + /* + * The file is the same. Ignore it. + */ + return 1; + } + + /* + * Not in the cache. + */ + return 0; +} + +typedef struct fr_pool_t { + void *page_end; + void *free_ptr; + struct fr_pool_t *page_free; + struct fr_pool_t *page_next; +} fr_pool_t; + +#define FR_POOL_SIZE (32768) +#define FR_ALLOC_ALIGN (8) + +static fr_pool_t *dict_pool = NULL; + +static fr_pool_t *fr_pool_create(void) +{ + fr_pool_t *fp = malloc(FR_POOL_SIZE); + + if (!fp) return NULL; + + memset(fp, 0, FR_POOL_SIZE); + + fp->page_end = ((uint8_t *) fp) + FR_POOL_SIZE; + fp->free_ptr = ((uint8_t *) fp) + sizeof(*fp); + fp->page_free = fp; + fp->page_next = NULL; + return fp; +} + +static void fr_pool_delete(fr_pool_t **pfp) +{ + fr_pool_t *fp, *next; + + if (!pfp || !*pfp) return; + + for (fp = *pfp; fp != NULL; fp = next) { + next = fp->page_next; + fp->page_next = NULL; + free(fp); + } + *pfp = NULL; +} + + +static void *fr_pool_alloc(size_t size) +{ + void *ptr; + + if (size == 0) return NULL; + + if (size > 256) return NULL; /* shouldn't happen */ + + if (!dict_pool) { + dict_pool = fr_pool_create(); + if (!dict_pool) return NULL; + } + + if ((size & (FR_ALLOC_ALIGN - 1)) != 0) { + size += FR_ALLOC_ALIGN - (size & (FR_ALLOC_ALIGN - 1)); + } + + if ((((uint8_t *) dict_pool->page_free->free_ptr) + size) > (uint8_t *) dict_pool->page_free->page_end) { + dict_pool->page_free->page_next = fr_pool_create(); + if (!dict_pool->page_free->page_next) return NULL; + dict_pool->page_free = dict_pool->page_free->page_next; + } + + ptr = dict_pool->page_free->free_ptr; + dict_pool->page_free->free_ptr = ((uint8_t *) dict_pool->page_free->free_ptr) + size; + + return ptr; +} + + +static void fr_pool_free(UNUSED void *ptr) +{ + /* + * Place-holder for later code. + */ +} + +/* + * Free the dictionary_attributes and dictionary_values lists. + */ +void dict_free(void) +{ + /* + * Free the tables + */ + fr_hash_table_free(vendors_byname); + fr_hash_table_free(vendors_byvalue); + vendors_byname = NULL; + vendors_byvalue = NULL; + + fr_hash_table_free(attributes_byname); + fr_hash_table_free(attributes_byvalue); + fr_hash_table_free(attributes_combo); + attributes_byname = NULL; + attributes_byvalue = NULL; + attributes_combo = NULL; + + fr_hash_table_free(values_byname); + fr_hash_table_free(values_byvalue); + values_byname = NULL; + values_byvalue = NULL; + + memset(dict_base_attrs, 0, sizeof(dict_base_attrs)); + + fr_pool_delete(&dict_pool); + + dict_stat_free(); +} + +/* + * Add vendor to the list. + */ +int dict_addvendor(char const *name, unsigned int value) +{ + size_t length; + DICT_VENDOR *dv; + + if (value >= FR_MAX_VENDOR) { + fr_strerror_printf("dict_addvendor: Cannot handle vendor ID larger than 2^24"); + return -1; + } + + if ((length = strlen(name)) >= DICT_VENDOR_MAX_NAME_LEN) { + fr_strerror_printf("dict_addvendor: vendor name too long"); + return -1; + } + + if ((dv = fr_pool_alloc(sizeof(*dv) + length)) == NULL) { + fr_strerror_printf("dict_addvendor: out of memory"); + return -1; + } + + strcpy(dv->name, name); + dv->vendorpec = value; + dv->type = dv->length = 1; /* defaults */ + + if (!fr_hash_table_insert(vendors_byname, dv)) { + DICT_VENDOR *old_dv; + + old_dv = fr_hash_table_finddata(vendors_byname, dv); + if (!old_dv) { + fr_strerror_printf("dict_addvendor: Failed inserting vendor name %s", name); + return -1; + } + if (old_dv->vendorpec != dv->vendorpec) { + fr_strerror_printf("dict_addvendor: Duplicate vendor name %s", name); + return -1; + } + + /* + * Already inserted. Discard the duplicate entry. + */ + fr_pool_free(dv); + return 0; + } + + /* + * Insert the SAME pointer (not free'd when this table is + * deleted), into another table. + * + * We want this behaviour because we want OLD names for + * the attributes to be read from the configuration + * files, but when we're printing them, (and looking up + * by value) we want to use the NEW name. + */ + if (!fr_hash_table_replace(vendors_byvalue, dv)) { + fr_strerror_printf("dict_addvendor: Failed inserting vendor %s", + name); + return -1; + } + + return 0; +} + +const int dict_attr_allowed_chars[256] = { +/* 0x 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +/* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 2 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, +/* 3 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, +/* 4 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 5 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, +/* 6 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 7 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, +/* 8 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 9 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* a */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* b */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* c */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* d */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* e */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * [a-zA-Z0-9_-:.]+ + */ +int dict_valid_name(char const *name) +{ + uint8_t const *p; + + for (p = (uint8_t const *) name; *p != '\0'; p++) { + if (!dict_attr_allowed_chars[*p]) { + char buff[5]; + + fr_prints(buff, sizeof(buff), (char const *)p, 1, '\''); + fr_strerror_printf("Invalid character '%s' in attribute", buff); + + return -(p - (uint8_t const *)name); + } + } + + return 0; +} + + +/* + * Find the parent of the attr/vendor. + */ +DICT_ATTR const *dict_parent(unsigned int attr, unsigned int vendor) +{ + int i; + unsigned int base_vendor; + + /* + * RFC attributes can't be of type "tlv", except for dictionary.rfc6930 + */ + if (!vendor) { +#ifdef PW_IPV6_6RD_CONFIGURATION + if (attr == PW_IPV6_6RD_CONFIGURATION) return NULL; + + if (((attr & 0xff) == PW_IPV6_6RD_CONFIGURATION) && + (attr >> 8) < 4) { + return dict_attrbyvalue(PW_IPV6_6RD_CONFIGURATION, 0); + } +#endif + return NULL; + } + + base_vendor = vendor & (FR_MAX_VENDOR - 1); + + /* + * It's a real vendor. + */ + if (base_vendor != 0) { + DICT_VENDOR const *dv; + + dv = dict_vendorbyvalue(base_vendor); + if (!dv) return NULL; + + /* + * Only standard format attributes can be of type "tlv", + * Except for DHCP. <sigh> + */ + if ((vendor != 54) && ((dv->type != 1) || (dv->length != 1))) return NULL; + + for (i = MAX_TLV_NEST; i > 0; i--) { + unsigned int parent; + + parent = attr & fr_attr_parent_mask[i]; + + if (parent != attr) return dict_attrbyvalue(parent, vendor); /* not base_vendor */ + } + + /* + * It was a top-level VSA. There's no parent. + * We COULD return the appropriate enclosing VSA + * (26, or 241.26, etc.) but that's not what we + * want. + */ + return NULL; + } + + /* + * It's an extended attribute. Return the base Extended-Attr-X + */ + if (attr < 256) return dict_attrbyvalue((vendor / FR_MAX_VENDOR) & 0xff, 0); + + /* + * Figure out which attribute it is. + */ + for (i = MAX_TLV_NEST; i > 0; i--) { + unsigned int parent; + + parent = attr & fr_attr_parent_mask[i]; + if (parent != attr) return dict_attrbyvalue(parent, vendor); /* not base_vendor */ + } + + return NULL; +} + + +/** Add an attribute to the dictionary + * + * @return 0 on success -1 on failure. + */ +int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, + ATTR_FLAGS flags) +{ + size_t namelen; + DICT_ATTR const *parent; + DICT_ATTR *n; + DICT_ATTR const *old; + static int max_attr = 0; + + namelen = strlen(name); + if (namelen >= DICT_ATTR_MAX_NAME_LEN) { + fr_strerror_printf("dict_addattr: attribute name too long"); + return -1; + } + + if (dict_valid_name(name) < 0) return -1; + + if (flags.has_tag && + !((type == PW_TYPE_INTEGER) || (type == PW_TYPE_STRING))) { + fr_strerror_printf("dict_addattr: Only 'integer' and 'string' attributes can have tags"); + return -1; + } + + /* + * Disallow attributes of type zero. + */ + if (!attr && !vendor) { + fr_strerror_printf("dict_addattr: Attribute 0 is invalid and cannot be used"); + return -1; + } + + /* + * If the attr is '-1', that means use a pre-existing + * one (if it already exists). If one does NOT already exist, + * then create a new attribute, with a non-conflicting value, + * and use that. + */ + if (attr == -1) { + if (dict_attrbyname(name)) { + return 0; /* exists, don't add it again */ + } + + attr = ++max_attr; + + } else if (vendor == 0) { + /* + * Update 'max_attr' + */ + if (attr > max_attr) { + max_attr = attr; + } + } + + /* + * Check the parent attribute, and set the various flags + * based on the parents values. It's OK for the caller + * to not set them, as we'll set them. But if the caller + * sets them when he's not supposed to set them, that's + * an error. + */ + parent = dict_parent(attr, vendor); + if (parent) { + /* + * We're still in the same space and the parent isn't a TLV. That's an error. + * + * Otherwise, dict_parent() has taken us from an Extended sub-attribute to + * a *the* Extended attribute, whish isn't what we want here. + */ + if ((vendor == parent->vendor) && (parent->type != PW_TYPE_TLV)) { + fr_strerror_printf("dict_addattr: Attribute %s has parent attribute %s which is not of type 'tlv'", + name, parent->name); + return -1; + } + + flags.extended |= parent->flags.extended; + flags.long_extended |= parent->flags.long_extended; + flags.evs |= parent->flags.evs; + } + + /* + * Manually extended flags for extended attributes. We + * can't expect the caller to know all of the details of the flags. + */ + if (vendor >= FR_MAX_VENDOR) { + DICT_ATTR const *da; + + /* + * Trying to manually create an extended + * attribute, but the parent extended attribute + * doesn't exist? That's an error. + */ + da = dict_attrbyvalue(vendor / FR_MAX_VENDOR, 0); + if (!da) { + fr_strerror_printf("Extended attributes must be defined from the extended space"); + return -1; + } + + flags.extended |= da->flags.extended; + flags.long_extended |= da->flags.long_extended; + flags.evs |= da->flags.evs; + + /* + * There's still a real vendor. Since it's an + * extended attribute, set the EVS flag. + */ + if ((vendor & (FR_MAX_VENDOR -1)) != 0) flags.evs = 1; + } + + /* + * Additional checks for extended attributes. + */ + if (flags.extended || flags.long_extended || flags.evs) { + if (vendor && (vendor < FR_MAX_VENDOR)) { + fr_strerror_printf("dict_addattr: VSAs cannot use the \"extended\" or \"evs\" attribute formats"); + return -1; + } + if (flags.has_tag +#ifdef WITH_DHCP + || flags.array +#endif + || ((flags.encrypt != FLAG_ENCRYPT_NONE) && (flags.encrypt != FLAG_ENCRYPT_TUNNEL_PASSWORD))) { + fr_strerror_printf("dict_addattr: The \"extended\" attributes MUST NOT have any flags set"); + return -1; + } + } + + if (flags.evs) { + if (!(flags.extended || flags.long_extended)) { + fr_strerror_printf("dict_addattr: Attributes of type \"evs\" MUST have a parent of type \"extended\""); + return -1; + } + } + + /* + * Do various sanity checks. + */ + if (attr < 0) { + fr_strerror_printf("dict_addattr: ATTRIBUTE has invalid number (less than zero)"); + return -1; + } + + if (flags.has_tlv && flags.length) { + fr_strerror_printf("TLVs cannot have a fixed length"); + return -1; + } + + if (vendor && flags.concat) { + fr_strerror_printf("VSAs cannot have the \"concat\" flag set"); + return -1; + } + + if (flags.concat && (type != PW_TYPE_OCTETS)) { + fr_strerror_printf("The \"concat\" flag can only be set for attributes of type \"octets\""); + return -1; + } + + if (flags.concat && (flags.has_tag || flags.array || flags.is_tlv || flags.has_tlv || + flags.length || flags.evs || flags.extended || flags.long_extended || + (flags.encrypt != FLAG_ENCRYPT_NONE))) { + fr_strerror_printf("The \"concat\" flag cannot be used with any other flag"); + return -1; + } + + if (flags.encrypt) flags.secret = 1; + + if (flags.length && (type != PW_TYPE_OCTETS)) { + fr_strerror_printf("The \"length\" flag can only be set for attributes of type \"octets\""); + return -1; + } + + if (flags.length && (flags.has_tag || flags.array || flags.is_tlv || flags.has_tlv || + flags.concat || flags.evs || flags.extended || flags.long_extended || + (flags.encrypt > FLAG_ENCRYPT_USER_PASSWORD))) { + fr_strerror_printf("The \"length\" flag cannot be used with any other flag"); + return -1; + } + + /* + * Force "length" for data types of fixed length; + */ + switch (type) { + case PW_TYPE_BYTE: + flags.length = 1; + break; + + case PW_TYPE_SHORT: + flags.length = 2; + break; + + case PW_TYPE_DATE: + case PW_TYPE_IPV4_ADDR: + case PW_TYPE_INTEGER: + case PW_TYPE_SIGNED: + flags.length = 4; + break; + + case PW_TYPE_INTEGER64: + flags.length = 8; + break; + + case PW_TYPE_ETHERNET: + flags.length = 6; + break; + + case PW_TYPE_IFID: + flags.length = 8; + break; + + case PW_TYPE_IPV6_ADDR: + flags.length = 16; + break; + + case PW_TYPE_EXTENDED: + if ((vendor != 0) || (attr < 241)) { + fr_strerror_printf("Attributes of type \"extended\" MUST be " + "RFC attributes with value >= 241."); + return -1; + } + + flags.length = 0; + flags.extended = 1; + break; + + case PW_TYPE_LONG_EXTENDED: + if ((vendor != 0) || (attr < 241)) { + fr_strerror_printf("Attributes of type \"long-extended\" MUST " + "be RFC attributes with value >= 241."); + return -1; + } + + flags.length = 0; + flags.extended = 1; + flags.long_extended = 1; + break; + + case PW_TYPE_EVS: + if (attr != PW_VENDOR_SPECIFIC) { + fr_strerror_printf("Attributes of type \"evs\" MUST have " + "attribute code 26."); + return -1; + } + + flags.length = 0; + flags.extended = 1; + flags.evs = 1; + break; + + case PW_TYPE_STRING: + case PW_TYPE_OCTETS: + case PW_TYPE_TLV: + flags.is_pointer = true; + break; + + default: + break; + } + + /* + * Stupid hacks for MS-CHAP-MPPE-Keys. The User-Password + * encryption method has no provisions for encoding the + * length of the data. For User-Password, the data is + * (presumably) all printable non-zero data. For + * MS-CHAP-MPPE-Keys, the data is binary crap. So... we + * MUST specify a length in the dictionary. + */ + if ((flags.encrypt == FLAG_ENCRYPT_USER_PASSWORD) && (type != PW_TYPE_STRING)) { + if (type != PW_TYPE_OCTETS) { + fr_strerror_printf("The \"encrypt=1\" flag cannot be used with non-string data types"); + return -1; + } + + if (flags.length == 0) { + fr_strerror_printf("The \"encrypt=1\" flag MUST be used with an explicit length for 'octets' data types"); + return -1; + } + } + + if ((vendor & (FR_MAX_VENDOR -1)) != 0) { + DICT_VENDOR *dv; + static DICT_VENDOR *last_vendor = NULL; + + if (flags.has_tlv && (flags.encrypt != FLAG_ENCRYPT_NONE)) { + fr_strerror_printf("TLV's cannot be encrypted"); + return -1; + } + + if (flags.is_tlv && flags.has_tag) { + fr_strerror_printf("Sub-TLV's cannot have a tag"); + return -1; + } + + if (flags.has_tlv && flags.has_tag) { + fr_strerror_printf("TLV's cannot have a tag"); + return -1; + } + + /* + * Most ATTRIBUTEs are bunched together by + * VENDOR. We can save a lot of lookups on + * dictionary initialization by caching the last + * vendor. + */ + if (last_vendor && + ((vendor & (FR_MAX_VENDOR - 1)) == last_vendor->vendorpec)) { + dv = last_vendor; + } else { + /* + * Ignore the high byte (sigh) + */ + dv = dict_vendorbyvalue(vendor & (FR_MAX_VENDOR - 1)); + last_vendor = dv; + } + + /* + * If the vendor isn't defined, die. + */ + if (!dv) { + fr_strerror_printf("dict_addattr: Unknown vendor %u", + vendor & (FR_MAX_VENDOR - 1)); + return -1; + } + + if (!attr && dv->type != 1) { + fr_strerror_printf("dict_addattr: Attribute %s cannot have value zero", + name); + return -1; + } + + /* + * FIXME: Switch over dv->type, and limit things + * properly. + */ + if ((dv->type == 1) && (attr >= 256) && !flags.is_tlv) { + fr_strerror_printf("dict_addattr: ATTRIBUTE has invalid number (larger than 255)"); + return -1; + } /* else 256..65535 are allowed */ + + /* + * <sigh> Alvarion, being *again* a horribly + * broken vendor, has re-used the WiMAX format in + * their proprietary vendor space. This re-use + * means that there are *multiple* conflicting + * Alvarion dictionaries. + */ + flags.wimax = dv->flags; + } /* it's a VSA of some kind */ + + /* + * Create a new attribute for the list + */ + if ((n = fr_pool_alloc(sizeof(*n) + namelen)) == NULL) { + oom: + fr_strerror_printf("dict_addattr: out of memory"); + return -1; + } + + memcpy(n->name, name, namelen); + n->name[namelen] = '\0'; + n->attr = attr; + n->vendor = vendor; + n->type = type; + n->flags = flags; + + /* + * Allow old-style names, but they always end up as + * new-style names. + */ + old = dict_attrbyvalue(n->attr, n->vendor); + if (old && (old->type == n->type)) { + DICT_ATTR *mutable; + + memcpy(&mutable, &old, sizeof(old)); /* const issues */ + mutable->flags.is_dup = true; + } + + /* + * Insert the attribute, only if it's not a duplicate. + */ + if (!fr_hash_table_insert(attributes_byname, n)) { + DICT_ATTR *a; + + /* + * If the attribute has identical number, then + * ignore the duplicate. + */ + a = fr_hash_table_finddata(attributes_byname, n); + if (a && (strcasecmp(a->name, n->name) == 0)) { + if (a->attr != n->attr) { + fr_strerror_printf("dict_addattr: Duplicate attribute name %s", name); + fr_pool_free(n); + return -1; + } + + /* + * Same name, same vendor, same attr, + * maybe the flags and/or type is + * different. Let the new value + * over-ride the old one. + */ + } + + + fr_hash_table_delete(attributes_byvalue, a); + + if (!fr_hash_table_replace(attributes_byname, n)) { + fr_strerror_printf("dict_addattr: Internal error storing attribute %s", name); + fr_pool_free(n); + return -1; + } + } + + /* + * Insert the SAME pointer (not free'd when this entry is + * deleted), into another table. + * + * We want this behaviour because we want OLD names for + * the attributes to be read from the configuration + * files, but when we're printing them, (and looking up + * by value) we want to use the NEW name. + */ + if (!fr_hash_table_replace(attributes_byvalue, n)) { + fr_strerror_printf("dict_addattr: Failed inserting attribute name %s", name); + return -1; + } + + /* + * Hacks for combo-IP + */ + if (n->type == PW_TYPE_COMBO_IP_ADDR) { + DICT_ATTR *v4, *v6; + + v4 = fr_pool_alloc(sizeof(*v4) + namelen); + if (!v4) goto oom; + + v6 = fr_pool_alloc(sizeof(*v6) + namelen); + if (!v6) goto oom; + + memcpy(v4, n, sizeof(*v4) + namelen); + v4->type = PW_TYPE_IPV4_ADDR; + + memcpy(v6, n, sizeof(*v6) + namelen); + v6->type = PW_TYPE_IPV6_ADDR; + if (!fr_hash_table_replace(attributes_combo, v4)) { + fr_strerror_printf("dict_addattr: Failed inserting attribute name %s - IPv4", name); + return -1; + } + + if (!fr_hash_table_replace(attributes_combo, v6)) { + fr_strerror_printf("dict_addattr: Failed inserting attribute name %s - IPv6", name); + return -1; + } + } + + if (!vendor && (attr > 0) && (attr < 256)) { + dict_base_attrs[attr] = n; + } + + return 0; +} + + +/* + * Add a value for an attribute to the dictionary. + */ +int dict_addvalue(char const *namestr, char const *attrstr, int value) +{ + size_t length; + DICT_ATTR const *da; + DICT_VALUE *dval; + + static DICT_ATTR const *last_attr = NULL; + + if (!*namestr) { + fr_strerror_printf("dict_addvalue: empty names are not permitted"); + return -1; + } + + if ((length = strlen(namestr)) >= DICT_VALUE_MAX_NAME_LEN) { + fr_strerror_printf("dict_addvalue: value name too long"); + return -1; + } + + if ((dval = fr_pool_alloc(sizeof(*dval) + length)) == NULL) { + fr_strerror_printf("dict_addvalue: out of memory"); + return -1; + } + memset(dval, 0, sizeof(*dval)); + + strcpy(dval->name, namestr); + dval->value = value; + + /* + * Most VALUEs are bunched together by ATTRIBUTE. We can + * save a lot of lookups on dictionary initialization by + * caching the last attribute. + */ + if (last_attr && (strcasecmp(attrstr, last_attr->name) == 0)) { + da = last_attr; + } else { + da = dict_attrbyname(attrstr); + last_attr = da; + } + + /* + * Remember which attribute is associated with this + * value, if possible. + */ + if (da) { + if (da->flags.has_value_alias) { + fr_strerror_printf("dict_addvalue: Cannot add VALUE for ATTRIBUTE \"%s\": It already has a VALUE-ALIAS", attrstr); + return -1; + } + + dval->attr = da->attr; + dval->vendor = da->vendor; + + /* + * Enforce valid values + * + * Don't worry about fixups... + */ + switch (da->type) { + case PW_TYPE_BYTE: + if (value > 255) { + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: ATTRIBUTEs of type 'byte' cannot have VALUEs larger than 255"); + return -1; + } + break; + case PW_TYPE_SHORT: + if (value > 65535) { + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: ATTRIBUTEs of type 'short' cannot have VALUEs larger than 65535"); + return -1; + } + break; + + /* + * Allow octets for now, because + * of dictionary.cablelabs + */ + case PW_TYPE_OCTETS: + + case PW_TYPE_INTEGER: + break; + + case PW_TYPE_INTEGER64: + default: + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: VALUEs cannot be defined for attributes of type '%s'", + fr_int2str(dict_attr_types, da->type, "?Unknown?")); + return -1; + } + /* in v4 this is done with the UNCONST #define */ + ((DICT_ATTR *)((uintptr_t)(da)))->flags.has_value = 1; + } else { + value_fixup_t *fixup; + + fixup = (value_fixup_t *) malloc(sizeof(*fixup)); + if (!fixup) { + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: out of memory"); + return -1; + } + memset(fixup, 0, sizeof(*fixup)); + + strlcpy(fixup->attrstr, attrstr, sizeof(fixup->attrstr)); + fixup->dval = dval; + + /* + * Insert to the head of the list. + */ + fixup->next = value_fixup; + value_fixup = fixup; + + return 0; + } + + /* + * Add the value into the dictionary. + */ + { + DICT_ATTR *tmp; + memcpy(&tmp, &dval, sizeof(tmp)); + + if (!fr_hash_table_insert(values_byname, tmp)) { + if (da) { + DICT_VALUE *old; + + /* + * Suppress duplicates with the same + * name and value. There are lots in + * dictionary.ascend. + */ + old = dict_valbyname(da->attr, da->vendor, namestr); + if (old && (old->value == dval->value)) { + fr_pool_free(dval); + return 0; + } + } + + fr_pool_free(dval); + fr_strerror_printf("dict_addvalue: Duplicate value name %s for attribute %s", namestr, attrstr); + return -1; + } + } + + /* + * There are multiple VALUE's, keyed by attribute, so we + * take care of that here. + */ + if (!fr_hash_table_replace(values_byvalue, dval)) { + fr_strerror_printf("dict_addvalue: Failed inserting value %s", + namestr); + return -1; + } + + return 0; +} + +static int sscanf_i(char const *str, unsigned int *pvalue) +{ + unsigned int rcode = 0; + int base = 10; + static char const *tab = "0123456789"; + + if ((str[0] == '0') && + ((str[1] == 'x') || (str[1] == 'X'))) { + tab = "0123456789abcdef"; + base = 16; + + str += 2; + } + + while (*str) { + char const *c; + + if (*str == '.') break; + + c = memchr(tab, tolower((uint8_t) *str), base); + if (!c) return 0; + + rcode *= base; + rcode += (c - tab); + str++; + } + + *pvalue = rcode; + return 1; +} + + +/* + * Get the OID based on various pieces of information. + * + * Remember, the packing format is weird. + * + * Vendor Attribute + * ------ --------- + * 00VID 000000AA normal VSA for vendor VID + * 00VID AABBCCDD normal VSAs with TLVs + * EE000 000000AA extended attr (241.1) + * EE000 AABBCCDD extended attr with TLVs + * EEVID 000000AA EVS with vendor VID, attr AAA + * EEVID AABBCCDD EVS with TLVs + * + * <whew>! Are we crazy, or what? + */ +int dict_str2oid(char const *ptr, unsigned int *pvalue, unsigned int *pvendor, + int tlv_depth) +{ + char const *p; + unsigned int attr; + +#ifdef WITH_DICT_OID_DEBUG + fprintf(stderr, "PARSING %s tlv_depth %d pvalue %08x pvendor %08x\n", ptr, + tlv_depth, *pvalue, *pvendor); +#endif + + if (tlv_depth > fr_attr_max_tlv) { + fr_strerror_printf("Too many sub-attributes"); + return -1; + } + + /* + * No vendor, try to do basic parsing. + */ + if (!*pvendor && !*pvalue) { + /* + * Can't call us with a pre-parsed value and no vendor. + */ + if (tlv_depth != 0) { + fr_strerror_printf("Invalid call with wrong TLV depth %d", tlv_depth); + return -1; + } + + p = strchr(ptr, '.'); + if (!sscanf_i(ptr, &attr)) { + fr_strerror_printf("Invalid data '%s' in attribute identifier", ptr); + return -1; + } + + /* + * Normal attribute with no OID. Return it. + */ + if (!p) { + *pvalue = attr; + goto done; + } + + /* + * We have an OID, look up the attribute to see what it is. + */ + if (attr != PW_VENDOR_SPECIFIC) { + DICT_ATTR const *da; + + da = dict_attrbyvalue(attr, 0); + if (!da) { + *pvalue = attr; + goto done; + } + + /* + * Standard attributes (including internal + * ones) can have TLVs, but only for some + * of them. + */ + if (!da->flags.extended) { +#ifdef PW_IPV6_6RD_CONFIGURATION + if (attr == PW_IPV6_6RD_CONFIGURATION) { + *pvalue = attr; + ptr = p + 1; + tlv_depth = 1; + goto keep_parsing; + } +#endif + fr_strerror_printf("Standard attributes cannot use OIDs"); + return -1; + } + + *pvendor = attr * FR_MAX_VENDOR; + ptr = p + 1; + } /* and fall through to re-parsing the VSA */ + + /* + * Look for the attribute number. + */ + if (!sscanf_i(ptr, &attr)) { + fr_strerror_printf("Invalid data '%s' in attribute identifier", ptr); + return -1; + } + + p = strchr(ptr, '.'); + + /* + * Handle VSAs. Either in the normal space, or in the extended space. + */ + if (attr == PW_VENDOR_SPECIFIC) { + if (!p) { + *pvalue = attr; + goto done; + } + ptr = p + 1; + + if (!sscanf_i(ptr, &attr)) { + fr_strerror_printf("Invalid data '%s' in vendor identifier", ptr); + return -1; + } + + p = strchr(ptr, '.'); + if (!p) { + fr_strerror_printf("Cannot define VENDOR in an ATTRIBUTE"); + return -1; + } + ptr = p + 1; + + *pvendor |= attr; + } else { + *pvalue = attr; + } + } /* fall through to processing an OID with pre-defined *pvendor and *pvalue */ + +keep_parsing: +#ifdef WITH_DICT_OID_DEBUG + fprintf(stderr, "KEEP PARSING %s tlv_depth %d pvalue %08x pvendor %08x\n", ptr, + tlv_depth, *pvalue, *pvendor); +#endif + + /* + * Check the vendor. Only RFC format attributes can have TLVs. + */ + if (*pvendor) { + DICT_VENDOR const *dv = NULL; + + dv = dict_vendorbyvalue(*pvendor); + if (dv && (dv->type != 1)) { + if (*pvalue || (tlv_depth != 0)) { + fr_strerror_printf("Attribute cannot have TLVs"); + return -1; + } + + if (!sscanf_i(ptr, &attr)) { + fr_strerror_printf("Invalid data '%s' in attribute identifier", ptr); + return -1; + } + + if ((dv->type < 3) && (attr > (unsigned int) (1 << (8 * dv->type)))) { + fr_strerror_printf("Number '%s' out of allowed range in attribute identifier", ptr); + return -1; + } + + *pvalue = attr; + +#ifdef WITH_DHCP + /* + * DHCP attributes can have TLVs. <sigh> + */ + if (*pvendor == 54) goto dhcp_skip; +#endif + goto done; + } + } + + /* + * Parse the rest of the TLVs. + */ + while (tlv_depth <= fr_attr_max_tlv) { +#ifdef WITH_DICT_OID_DEBUG + fprintf(stderr, "TLV PARSING %s tlv_depth %d pvalue %08x pvendor %08x\n", ptr, + tlv_depth, *pvalue, *pvendor); +#endif + + if (!sscanf_i(ptr, &attr)) { + fr_strerror_printf("Invalid data '%s' in attribute identifier", ptr); + return -1; + } + + if (attr > fr_attr_mask[tlv_depth]) { + fr_strerror_printf("Number '%s' out of allowed range in attribute identifier", ptr); + return -1; + } + + attr <<= fr_attr_shift[tlv_depth]; + +#ifdef WITH_DICT_OID_DEBUG + if (*pvendor) { + DICT_ATTR const *da; + + da = dict_parent(*pvalue | attr, *pvendor); + if (!da) { + fprintf(stderr, "STR2OID FAILED PARENT %08x | %08x, %08x\n", + *pvalue, attr, *pvendor); + } else if ((da->attr != *pvalue) || (da->vendor != *pvendor)) { + fprintf(stderr, "STR2OID DISAGREEMENT WITH PARENT %08x, %08x\t%08x, %08x\n", + *pvalue, *pvendor, da->attr, da->vendor); + } + } +#endif + + *pvalue |= attr; + +#ifdef WITH_DHCP + dhcp_skip: +#endif + p = strchr(ptr, '.'); + if (!p) break; + + ptr = p + 1; + tlv_depth++; + } + +done: +#ifdef WITH_DICT_OID_DEBUG + fprintf(stderr, "RETURNING %08x %08x\n", *pvalue, *pvendor); +#endif + return 0; +} + + +/* + * Process the ATTRIBUTE command + */ +static int process_attribute(char const* fn, int const line, + unsigned int block_vendor, + DICT_ATTR const *block_tlv, int tlv_depth, + char **argv, int argc) +{ + int oid = 0; + unsigned int vendor = 0; + unsigned int value; + int type; + unsigned int length; + ATTR_FLAGS flags; + char *p; + + if ((argc < 3) || (argc > 4)) { + fr_strerror_printf("dict_init: %s[%d]: invalid ATTRIBUTE line", + fn, line); + return -1; + } + + /* + * Dictionaries need to have real names, not shitty ones. + */ + if (strncmp(argv[0], "Attr-", 5) == 0) { + fr_strerror_printf("dict_init: %s[%d]: Invalid attribute name", + fn, line); + return -1; + } + + memset(&flags, 0, sizeof(flags)); + + /* + * Look for OIDs before doing anything else. + */ + if (strchr(argv[1], '.') != NULL) oid = 1; + + { + DICT_ATTR const *da; + + vendor = block_vendor; + + if (!block_tlv) { + value = 0; + } else { + value = block_tlv->attr; + } + + /* + * Parse OID. + */ + if (dict_str2oid(argv[1], &value, &vendor, tlv_depth) < 0) { + char buffer[256]; + + strlcpy(buffer, fr_strerror(), sizeof(buffer)); + + fr_strerror_printf("dict_init: %s[%d]: Invalid attribute identifier: %s", fn, line, buffer); + return -1; + } + block_vendor = vendor; + + if (oid) { + /* + * Set the flags based on the parents flags. + */ + da = dict_parent(value, vendor); + if (!da) { + fr_strerror_printf("dict_init: %s[%d]: Parent attribute for %08x,%08x is undefined.", fn, line, value, vendor); + return -1; + } + + flags.extended = da->flags.extended; + flags.long_extended = da->flags.long_extended; + flags.evs = da->flags.evs; + if (da->flags.has_tlv) flags.is_tlv = 1; + } + } + + if (strncmp(argv[2], "octets[", 7) != 0) { + /* + * find the type of the attribute. + */ + type = fr_str2int(dict_attr_types, argv[2], -1); + if (type < 0) { + fr_strerror_printf("dict_init: %s[%d]: invalid type \"%s\"", + fn, line, argv[2]); + return -1; + } + + } else { + type = PW_TYPE_OCTETS; + + p = strchr(argv[2] + 7, ']'); + if (!p) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for octets", fn, line); + return -1; + } + + *p = 0; + + if (!sscanf_i(argv[2] + 7, &length)) { + fr_strerror_printf("dict_init: %s[%d]: invalid length", fn, line); + return -1; + } + + if ((length == 0) || (length > 253)) { + fr_strerror_printf("dict_init: %s[%d]: invalid length", fn, line); + return -1; + } + + flags.length = length; + } + + /* + * Parse options. + */ + if (argc >= 4) { + char *key, *next, *last; + + /* + * Keep it real. + */ + if (flags.extended) { + fr_strerror_printf("dict_init: %s[%d]: Extended attributes cannot use flags", fn, line); + return -1; + } + + key = argv[3]; + do { + next = strchr(key, ','); + if (next) *(next++) = '\0'; + + /* + * Boolean flag, means this is a tagged + * attribute. + */ + if ((strcmp(key, "has_tag") == 0) || (strcmp(key, "has_tag=1") == 0)) { + flags.has_tag = 1; + + /* + * Encryption method, defaults to 0 (none). + * Currently valid is just type 2, + * Tunnel-Password style, which can only + * be applied to strings. + */ + } else if (strncmp(key, "encrypt=", 8) == 0) { + flags.encrypt = strtol(key + 8, &last, 0); + if (*last) { + fr_strerror_printf("dict_init: %s[%d] invalid option %s", + fn, line, key); + return -1; + } + + if ((flags.encrypt == FLAG_ENCRYPT_ASCEND_SECRET) && + (type != PW_TYPE_STRING)) { + fr_strerror_printf("dict_init: %s[%d] Only \"string\" types can have the " + "\"encrypt=3\" flag set", fn, line); + return -1; + } + flags.secret = 1; + + } else if (strncmp(key, "secret", 6) == 0) { + flags.secret = 1; + + } else if (strncmp(key, "array", 6) == 0) { + flags.array = 1; + + switch (type) { + case PW_TYPE_IPV4_ADDR: + case PW_TYPE_IPV6_ADDR: + case PW_TYPE_BYTE: + case PW_TYPE_SHORT: + case PW_TYPE_INTEGER: + case PW_TYPE_DATE: + case PW_TYPE_STRING: + break; + + default: + fr_strerror_printf("dict_init: %s[%d] \"%s\" type cannot have the " + "\"array\" flag set", + fn, line, + fr_int2str(dict_attr_types, type, "<UNKNOWN>")); + return -1; + } + + } else if (strncmp(key, "concat", 7) == 0) { + flags.concat = 1; + + if (type != PW_TYPE_OCTETS) { + fr_strerror_printf("dict_init: %s[%d] Only \"octets\" type can have the " + "\"concat\" flag set", fn, line); + return -1; + } + + } else if (strncmp(key, "virtual", 8) == 0) { + flags.virtual = 1; + + if (vendor != 0) { + fr_strerror_printf("dict_init: %s[%d] VSAs cannot have the \"virtual\" " + "flag set", fn, line); + return -1; + } + + if (value < 256) { + fr_strerror_printf("dict_init: %s[%d] Standard attributes cannot " + "have the \"virtual\" flag set", fn, line); + return -1; + } + + /* + * The only thing is the vendor name, + * and it's a known name: allow it. + */ + } else if ((key == argv[3]) && !next) { + if (oid) { + fr_strerror_printf("dict_init: %s[%d] New-style attributes cannot use " + "a vendor flag", fn, line); + return -1; + } + + if (block_vendor) { + fr_strerror_printf("dict_init: %s[%d] Vendor flag inside of \"BEGIN-VENDOR\" " + "is not allowed", fn, line); + return -1; + } + + vendor = dict_vendorbyname(key); + if (!vendor) goto unknown; + break; + + } else { + unknown: + fr_strerror_printf("dict_init: %s[%d]: unknown option \"%s\"", fn, line, key); + return -1; + } + + key = next; + if (key && !*key) break; + } while (key); + } + + if (block_vendor) vendor = block_vendor; + + /* + * Special checks for tags, they make our life much more + * difficult. + */ + if (flags.has_tag) { + /* + * Only string, octets, and integer can be tagged. + */ + switch (type) { + case PW_TYPE_STRING: + case PW_TYPE_INTEGER: + break; + + default: + fr_strerror_printf("dict_init: %s[%d]: Attributes of type %s cannot be tagged.", + fn, line, + fr_int2str(dict_attr_types, type, "?Unknown?")); + return -1; + } + } + + if (type == PW_TYPE_TLV) { + if (vendor && (vendor < FR_MAX_VENDOR) +#ifdef WITH_DHCP + && (vendor != DHCP_MAGIC_VENDOR) +#endif + ) { + DICT_VENDOR *dv; + + dv = dict_vendorbyvalue(vendor); + if (!dv || (dv->type != 1) || (dv->length != 1)) { + fr_strerror_printf("dict_init: %s[%d]: Type \"tlv\" can only be for \"format=1,1\".", + fn, line); + return -1; + } + + } + flags.has_tlv = 1; + } + + if (block_tlv) { + /* + * TLV's can be only one octet. + */ + if ((value == 0) || ((value & ~fr_attr_mask[tlv_depth]) != 0)) { + fr_strerror_printf( "dict_init: %s[%d]: sub-tlv has invalid attribute number", + fn, line); + return -1; + } + + /* + * Shift the value left. + */ + value <<= fr_attr_shift[tlv_depth]; + value |= block_tlv->attr; + flags.is_tlv = 1; + } + +#ifdef WITH_DICTIONARY_WARNINGS + /* + * Hack to help us discover which vendors have illegal + * attributes. + */ + if (!vendor && (value < 256) && + !strstr(fn, "rfc") && !strstr(fn, "illegal")) { + fprintf(stderr, "WARNING: Illegal Attribute %s in %s\n", + argv[0], fn); + } +#endif + + /* + * Add it in. + */ + if (dict_addattr(argv[0], value, vendor, type, flags) < 0) { + char buffer[256]; + + strlcpy(buffer, fr_strerror(), sizeof(buffer)); + + fr_strerror_printf("dict_init: %s[%d]: %s", + fn, line, buffer); + return -1; + } + + return 0; +} + + +/* + * Process the VALUE command + */ +static int process_value(char const* fn, int const line, char **argv, + int argc) +{ + unsigned int value; + + if (argc != 3) { + fr_strerror_printf("dict_init: %s[%d]: invalid VALUE line", + fn, line); + return -1; + } + /* + * For Compatibility, skip "Server-Config" + */ + if (strcasecmp(argv[0], "Server-Config") == 0) + return 0; + + /* + * Validate all entries + */ + if (!sscanf_i(argv[2], &value)) { + fr_strerror_printf("dict_init: %s[%d]: invalid value", + fn, line); + return -1; + } + + if (dict_addvalue(argv[1], argv[0], value) < 0) { + char buffer[256]; + + strlcpy(buffer, fr_strerror(), sizeof(buffer)); + + fr_strerror_printf("dict_init: %s[%d]: %s", + fn, line, buffer); + return -1; + } + + return 0; +} + + +/* + * Process the VALUE-ALIAS command + * + * This allows VALUE mappings to be shared among multiple + * attributes. + */ +static int process_value_alias(char const* fn, int const line, char **argv, + int argc) +{ + DICT_ATTR const *my_da, *da; + DICT_VALUE *dval; + + if (argc != 2) { + fr_strerror_printf("dict_init: %s[%d]: invalid VALUE-ALIAS line", + fn, line); + return -1; + } + + my_da = dict_attrbyname(argv[0]); + if (!my_da) { + fr_strerror_printf("dict_init: %s[%d]: ATTRIBUTE \"%s\" does not exist", + fn, line, argv[1]); + return -1; + } + + if (my_da->flags.has_value_alias) { + fr_strerror_printf("dict_init: %s[%d]: Cannot add VALUE-ALIAS to ATTRIBUTE \"%s\" with pre-existing VALUE-ALIAS", + fn, line, argv[0]); + return -1; + } + + da = dict_attrbyname(argv[1]); + if (!da) { + fr_strerror_printf("dict_init: %s[%d]: Cannot find ATTRIBUTE \"%s\" for alias", + fn, line, argv[1]); + return -1; + } + + if (da->flags.has_value_alias) { + fr_strerror_printf("dict_init: %s[%d]: Cannot add VALUE-ALIAS to ATTRIBUTE \"%s\" which itself has a VALUE-ALIAS", + fn, line, argv[1]); + return -1; + } + + if (my_da->type != da->type) { + fr_strerror_printf("dict_init: %s[%d]: Cannot add VALUE-ALIAS between attributes of differing type", + fn, line); + return -1; + } + + if ((dval = fr_pool_alloc(sizeof(*dval))) == NULL) { + fr_strerror_printf("dict_addvalue: out of memory"); + return -1; + } + + dval->name[0] = '\0'; /* empty name */ + dval->attr = my_da->attr; + dval->vendor = my_da->vendor; + dval->value = da->attr; + + if (!fr_hash_table_insert(values_byname, dval)) { + fr_strerror_printf("dict_init: %s[%d]: Error create alias", + fn, line); + fr_pool_free(dval); + return -1; + } + + return 0; +} + + +static int parse_format(char const *fn, int line, char const *format, int *ptype, int *plength, bool *pcontinuation) +{ + char const *p; + int type, length; + bool continuation = false; + + if (strncasecmp(format, "format=", 7) != 0) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for VENDOR. Expected \"format=\", got \"%s\"", + fn, line, format); + return -1; + } + + p = format + 7; + if ((strlen(p) < 3) || + !isdigit((uint8_t) p[0]) || + (p[1] != ',') || + !isdigit((uint8_t) p[2]) || + (p[3] && (p[3] != ','))) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for VENDOR. Expected text like \"1,1\", got \"%s\"", + fn, line, p); + return -1; + } + + type = (int) (p[0] - '0'); + length = (int) (p[2] - '0'); + + if ((type != 1) && (type != 2) && (type != 4)) { + fr_strerror_printf("dict_init: %s[%d]: invalid type value %d for VENDOR", + fn, line, type); + return -1; + } + + if ((length != 0) && (length != 1) && (length != 2)) { + fr_strerror_printf("dict_init: %s[%d]: invalid length value %d for VENDOR", + fn, line, length); + return -1; + } + + if (p[3] == ',') { + if (!p[4]) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for VENDOR. Expected text like \"1,1\", got \"%s\"", + fn, line, p); + return -1; + } + + if ((p[4] != 'c') || + (p[5] != '\0')) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for VENDOR. Expected text like \"1,1\", got \"%s\"", + fn, line, p); + return -1; + } + continuation = true; + + if ((type != 1) || (length != 1)) { + fr_strerror_printf("dict_init: %s[%d]: Only 'format=1,1' VSAs can have continuations", + fn, line); + return -1; + } + } + + *ptype = type; + *plength = length; + *pcontinuation = continuation; + return 0; +} + + +/* + * Process the VENDOR command + */ +static int process_vendor(char const* fn, int const line, char **argv, + int argc) +{ + int value; + int type, length; + bool continuation = false; + DICT_VENDOR *dv; + + if ((argc < 2) || (argc > 3)) { + fr_strerror_printf( "dict_init: %s[%d] invalid VENDOR entry", + fn, line); + return -1; + } + + /* + * Validate all entries + */ + if (!isdigit((uint8_t) argv[1][0])) { + fr_strerror_printf("dict_init: %s[%d]: invalid value", + fn, line); + return -1; + } + value = atoi(argv[1]); + + /* Create a new VENDOR entry for the list */ + if (dict_addvendor(argv[0], value) < 0) { + char buffer[256]; + + strlcpy(buffer, fr_strerror(), sizeof(buffer)); + + fr_strerror_printf("dict_init: %s[%d]: %s", + fn, line, buffer); + return -1; + } + + /* + * Look for a format statement. Allow it to over-ride the hard-coded formats below. + */ + if (argc == 3) { + if (parse_format(fn, line, argv[2], &type, &length, &continuation) < 0) { + return -1; + } + + } else if (value == VENDORPEC_USR) { /* catch dictionary screw-ups */ + type = 4; + length = 0; + + } else if (value == VENDORPEC_LUCENT) { + type = 2; + length = 1; + + } else if (value == VENDORPEC_STARENT) { + type = 2; + length = 2; + + } else { + type = length = 1; + } + + dv = dict_vendorbyvalue(value); + if (!dv) { + fr_strerror_printf("dict_init: %s[%d]: Failed adding format for VENDOR", + fn, line); + return -1; + } + + dv->type = type; + dv->length = length; + dv->flags = continuation; + + return 0; +} + +/* + * String split routine. Splits an input string IN PLACE + * into pieces, based on spaces. + */ +int str2argv(char *str, char **argv, int max_argc) +{ + int argc = 0; + + while (*str) { + if (argc >= max_argc) break; + + /* + * Chop out comments early. + */ + if (*str == '#') { + *str = '\0'; + break; + } + + while ((*str == ' ') || + (*str == '\t') || + (*str == '\r') || + (*str == '\n')) *(str++) = '\0'; + + if (!*str) break; + + argv[argc] = str; + argc++; + + while (*str && + (*str != ' ') && + (*str != '\t') && + (*str != '\r') && + (*str != '\n')) str++; + } + + return argc; +} + +static int my_dict_init(char const *parent, char const *filename, + char const *src_file, int src_line); + +int dict_read(char const *dir, char const *filename) +{ + if (!attributes_byname) { + fr_strerror_printf("Must call dict_init() before dict_read()"); + return -1; + } + + return my_dict_init(dir, filename, NULL, 0); +} + + +#define MAX_ARGV (16) + +/* + * Initialize the dictionary. + */ +static int my_dict_init(char const *parent, char const *filename, + char const *src_file, int src_line) +{ + FILE *fp; + char dir[256], fn[256]; + char buf[256]; + char *p; + int line = 0; + unsigned int vendor; + unsigned int block_vendor; + struct stat statbuf; + char *argv[MAX_ARGV]; + int argc; + DICT_ATTR const *da, *block_tlv[MAX_TLV_NEST + 1]; + int which_block_tlv = 0; + + block_tlv[0] = NULL; + block_tlv[1] = NULL; + block_tlv[2] = NULL; + block_tlv[3] = NULL; + + if ((strlen(parent) + 3 + strlen(filename)) > sizeof(dir)) { + fr_strerror_printf("dict_init: filename name too long"); + return -1; + } + + /* + * If it's an absolute dir, forget the parent dir, + * and remember the new one. + * + * If it's a relative dir, tack on the current filename + * to the parent dir. And use that. + */ + if (!FR_DIR_IS_RELATIVE(filename)) { + strlcpy(dir, filename, sizeof(dir)); + p = strrchr(dir, FR_DIR_SEP); + if (p) { + p[1] = '\0'; + } else { + strlcat(dir, "/", sizeof(dir)); + } + + strlcpy(fn, filename, sizeof(fn)); + } else { + strlcpy(dir, parent, sizeof(dir)); + p = strrchr(dir, FR_DIR_SEP); + if (p) { + if (p[1]) strlcat(dir, "/", sizeof(dir)); + } else { + strlcat(dir, "/", sizeof(dir)); + } + strlcat(dir, filename, sizeof(dir)); + p = strrchr(dir, FR_DIR_SEP); + if (p) { + p[1] = '\0'; + } else { + strlcat(dir, "/", sizeof(dir)); + } + + p = strrchr(filename, FR_DIR_SEP); + if (p) { + snprintf(fn, sizeof(fn), "%s%s", dir, p); + } else { + snprintf(fn, sizeof(fn), "%s%s", dir, filename); + } + + } + + /* + * Check if we've loaded this file before. If so, ignore it. + */ + p = strrchr(fn, FR_DIR_SEP); + if (p) { + *p = '\0'; + if (dict_stat_check(fn, p + 1)) { + *p = FR_DIR_SEP; + return 0; + } + *p = FR_DIR_SEP; + } + + if ((fp = fopen(fn, "r")) == NULL) { + if (!src_file) { + fr_strerror_printf("dict_init: Couldn't open dictionary \"%s\": %s", + fn, fr_syserror(errno)); + } else { + fr_strerror_printf("dict_init: %s[%d]: Couldn't open dictionary \"%s\": %s", + src_file, src_line, fn, fr_syserror(errno)); + } + return -2; + } + + stat(fn, &statbuf); /* fopen() guarantees this will succeed */ + if (!S_ISREG(statbuf.st_mode)) { + fclose(fp); + fr_strerror_printf("dict_init: Dictionary \"%s\" is not a regular file", + fn); + return -1; + } + + /* + * Globally writable dictionaries means that users can control + * the server configuration with little difficulty. + */ +#ifdef S_IWOTH + if ((statbuf.st_mode & S_IWOTH) != 0) { + fclose(fp); + fr_strerror_printf("dict_init: Dictionary \"%s\" is globally writable. Refusing to start due to insecure configuration.", + fn); + return -1; + } +#endif + + dict_stat_add(&statbuf); + + /* + * Seed the random pool with data. + */ + fr_rand_seed(&statbuf, sizeof(statbuf)); + + block_vendor = 0; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + line++; + if (buf[0] == '#' || buf[0] == 0 || + buf[0] == '\n' || buf[0] == '\r') + continue; + + /* + * Comment characters should NOT be appearing anywhere but + * as start of a comment; + */ + p = strchr(buf, '#'); + if (p) *p = '\0'; + + argc = str2argv(buf, argv, MAX_ARGV); + if (argc == 0) continue; + + if (argc == 1) { + fr_strerror_printf( "dict_init: %s[%d] invalid entry", + fn, line); + fclose(fp); + return -1; + } + + /* + * Process VALUE lines. + */ + if (strcasecmp(argv[0], "VALUE") == 0) { + if (process_value(fn, line, + argv + 1, argc - 1) == -1) { + fclose(fp); + return -1; + } + continue; + } + + /* + * Perhaps this is an attribute. + */ + if (strcasecmp(argv[0], "ATTRIBUTE") == 0) { + if (process_attribute(fn, line, block_vendor, + block_tlv[which_block_tlv], + which_block_tlv, + argv + 1, argc - 1) == -1) { + fclose(fp); + return -1; + } + continue; + } + + /* + * See if we need to import another dictionary. + */ + if (strcasecmp(argv[0], "$INCLUDE") == 0) { + if (my_dict_init(dir, argv[1], fn, line) < 0) { + fclose(fp); + return -1; + } + continue; + } /* $INCLUDE */ + + /* + * Optionally include a dictionary + */ + if ((strcasecmp(argv[0], "$INCLUDE-") == 0) || + (strcasecmp(argv[0], "$-INCLUDE") == 0)) { + int rcode = my_dict_init(dir, argv[1], fn, line); + + if (rcode == -2) continue; + + if (rcode < 0) { + fclose(fp); + return -1; + } + continue; + } /* $INCLUDE- */ + + if (strcasecmp(argv[0], "VALUE-ALIAS") == 0) { + if (process_value_alias(fn, line, + argv + 1, argc - 1) == -1) { + fclose(fp); + return -1; + } + continue; + } + + /* + * Process VENDOR lines. + */ + if (strcasecmp(argv[0], "VENDOR") == 0) { + if (process_vendor(fn, line, + argv + 1, argc - 1) == -1) { + fclose(fp); + return -1; + } + continue; + } + + if (strcasecmp(argv[0], "BEGIN-TLV") == 0) { + if (argc != 2) { + fr_strerror_printf( + "dict_init: %s[%d] invalid BEGIN-TLV entry", + fn, line); + fclose(fp); + return -1; + } + + da = dict_attrbyname(argv[1]); + if (!da) { + fr_strerror_printf( + "dict_init: %s[%d]: unknown attribute %s", + fn, line, argv[1]); + fclose(fp); + return -1; + } + + if (da->type != PW_TYPE_TLV) { + fr_strerror_printf( + "dict_init: %s[%d]: attribute %s is not of type tlv", + fn, line, argv[1]); + fclose(fp); + return -1; + } + + if (which_block_tlv >= MAX_TLV_NEST) { + fr_strerror_printf( + "dict_init: %s[%d]: TLVs are nested too deep", + fn, line); + fclose(fp); + return -1; + } + + + block_tlv[++which_block_tlv] = da; + continue; + } /* BEGIN-TLV */ + + if (strcasecmp(argv[0], "END-TLV") == 0) { + if (argc != 2) { + fr_strerror_printf( + "dict_init: %s[%d] invalid END-TLV entry", + fn, line); + fclose(fp); + return -1; + } + + da = dict_attrbyname(argv[1]); + if (!da) { + fr_strerror_printf( + "dict_init: %s[%d]: unknown attribute %s", + fn, line, argv[1]); + fclose(fp); + return -1; + } + + if (da != block_tlv[which_block_tlv]) { + fr_strerror_printf( + "dict_init: %s[%d]: END-TLV %s does not match any previous BEGIN-TLV", + fn, line, argv[1]); + fclose(fp); + return -1; + } + block_tlv[which_block_tlv--] = NULL; + continue; + } /* END-VENDOR */ + + if (strcasecmp(argv[0], "BEGIN-VENDOR") == 0) { + if (argc < 2) { + fr_strerror_printf( + "dict_init: %s[%d] invalid BEGIN-VENDOR entry", + fn, line); + fclose(fp); + return -1; + } + + vendor = dict_vendorbyname(argv[1]); + if (!vendor) { + fr_strerror_printf( + "dict_init: %s[%d]: unknown vendor %s", + fn, line, argv[1]); + fclose(fp); + return -1; + } + + block_vendor = vendor; + + /* + * Check for extended attr VSAs + * + * BEGIN-VENDOR foo format=Foo-Encapsulation-Attr + */ + if (argc > 2) { + if ((strncmp(argv[2], "format=", 7) != 0) && + (strncmp(argv[2], "parent=", 7) != 0)) { + fr_strerror_printf( + "dict_init: %s[%d]: Invalid format %s", + fn, line, argv[2]); + fclose(fp); + return -1; + } + + p = argv[2] + 7; + da = dict_attrbyname(p); + if (!da) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for BEGIN-VENDOR: unknown attribute \"%s\"", + fn, line, p); + fclose(fp); + return -1; + } + + if (!da->flags.evs) { + fr_strerror_printf("dict_init: %s[%d]: Invalid format for BEGIN-VENDOR. Attribute \"%s\" is not of \"evs\" data type", + fn, line, p); + fclose(fp); + return -1; + } + + /* + * Pack the encapsulating + * attribute into the upper 8 + * bits of the vendor ID + */ + block_vendor |= da->vendor; + } + + continue; + } /* BEGIN-VENDOR */ + + if (strcasecmp(argv[0], "END-VENDOR") == 0) { + if (argc != 2) { + fr_strerror_printf( + "dict_init: %s[%d] invalid END-VENDOR entry", + fn, line); + fclose(fp); + return -1; + } + + vendor = dict_vendorbyname(argv[1]); + if (!vendor) { + fr_strerror_printf( + "dict_init: %s[%d]: unknown vendor %s", + fn, line, argv[1]); + fclose(fp); + return -1; + } + + if (vendor != (block_vendor & (FR_MAX_VENDOR - 1))) { + fr_strerror_printf( + "dict_init: %s[%d]: END-VENDOR %s does not match any previous BEGIN-VENDOR", + fn, line, argv[1]); + fclose(fp); + return -1; + } + block_vendor = 0; + continue; + } /* END-VENDOR */ + + /* + * Any other string: We don't recognize it. + */ + fr_strerror_printf("dict_init: %s[%d] invalid keyword \"%s\"", + fn, line, argv[0]); + fclose(fp); + return -1; + } + fclose(fp); + return 0; +} + + +/* + * Empty callback for hash table initialization. + */ +static int null_callback(UNUSED void *ctx, UNUSED void *data) +{ + return 0; +} + + +/* + * Initialize the directory, then fix the attr member of + * all attributes. + */ +int dict_init(char const *dir, char const *fn) +{ + /* + * Check if we need to change anything. If not, don't do + * anything. + */ + if (dict_stat_check(dir, fn)) { + return 0; + } + + /* + * Free the dictionaries, and the stat cache. + */ + dict_free(); + + /* + * Create the table of vendor by name. There MAY NOT + * be multiple vendors of the same name. + * + * Each vendor is malloc'd, so the free function is free. + */ + vendors_byname = fr_hash_table_create(dict_vendor_name_hash, + dict_vendor_name_cmp, + fr_pool_free); + if (!vendors_byname) { + return -1; + } + + /* + * Create the table of vendors by value. There MAY + * be vendors of the same value. If there are, we + * pick the latest one. + */ + vendors_byvalue = fr_hash_table_create(dict_vendor_value_hash, + dict_vendor_value_cmp, + fr_pool_free); + if (!vendors_byvalue) { + return -1; + } + + /* + * Create the table of attributes by name. There MAY NOT + * be multiple attributes of the same name. + * + * Each attribute is malloc'd, so the free function is free. + */ + attributes_byname = fr_hash_table_create(dict_attr_name_hash, + dict_attr_name_cmp, + fr_pool_free); + if (!attributes_byname) { + return -1; + } + + /* + * Create the table of attributes by value. There MAY + * be attributes of the same value. If there are, we + * pick the latest one. + */ + attributes_byvalue = fr_hash_table_create(dict_attr_value_hash, + dict_attr_value_cmp, + fr_pool_free); + if (!attributes_byvalue) { + return -1; + } + + /* + * Horrible hacks for combo-IP. + */ + attributes_combo = fr_hash_table_create(dict_attr_combo_hash, + dict_attr_combo_cmp, + fr_pool_free); + if (!attributes_combo) { + return -1; + } + + values_byname = fr_hash_table_create(dict_value_name_hash, + dict_value_name_cmp, + fr_pool_free); + if (!values_byname) { + return -1; + } + + values_byvalue = fr_hash_table_create(dict_value_value_hash, + dict_value_value_cmp, + fr_pool_free); + if (!values_byvalue) { + return -1; + } + + value_fixup = NULL; /* just to be safe. */ + + if (my_dict_init(dir, fn, NULL, 0) < 0) + return -1; + + if (value_fixup) { + DICT_ATTR const *a; + value_fixup_t *this, *next; + + for (this = value_fixup; this != NULL; this = next) { + next = this->next; + + a = dict_attrbyname(this->attrstr); + if (!a) { + fr_strerror_printf( + "dict_init: No ATTRIBUTE \"%s\" defined for VALUE \"%s\"", + this->attrstr, this->dval->name); + return -1; /* leak, but they should die... */ + } + + this->dval->attr = a->attr; + + /* + * Add the value into the dictionary. + */ + if (!fr_hash_table_replace(values_byname, + this->dval)) { + fr_strerror_printf("dict_addvalue: Duplicate value name %s for attribute %s", this->dval->name, a->name); + return -1; + } + + /* + * Allow them to use the old name, but + * prefer the new name when printing + * values. + */ + if (!fr_hash_table_finddata(values_byvalue, this->dval)) { + fr_hash_table_replace(values_byvalue, + this->dval); + } + free(this); + + /* + * Just so we don't lose track of things. + */ + value_fixup = next; + } + } + + /* + * Walk over all of the hash tables to ensure they're + * initialized. We do this because the threads may perform + * lookups, and we don't want multi-threaded re-ordering + * of the table entries. That would be bad. + */ + fr_hash_table_walk(vendors_byname, null_callback, NULL); + fr_hash_table_walk(vendors_byvalue, null_callback, NULL); + + fr_hash_table_walk(attributes_byname, null_callback, NULL); + fr_hash_table_walk(attributes_byvalue, null_callback, NULL); + + fr_hash_table_walk(values_byvalue, null_callback, NULL); + fr_hash_table_walk(values_byname, null_callback, NULL); + + return 0; +} + +static size_t print_attr_oid(char *buffer, size_t bufsize, unsigned int attr, unsigned int vendor) +{ + int nest, dv_type = 1; + size_t len; + char *p = buffer; + + if (vendor > FR_MAX_VENDOR) { + len = snprintf(p, bufsize, "%u.", vendor / FR_MAX_VENDOR); + p += len; + bufsize -= len; + vendor &= (FR_MAX_VENDOR) - 1; + } + + if (vendor) { + DICT_VENDOR *dv; + + /* + * dv_type is the length of the vendor's type field + * RFC 2865 never defined a mandatory length, so + * different vendors have different length type fields. + */ + dv = dict_vendorbyvalue(vendor); + if (dv) dv_type = dv->type; + + len = snprintf(p, bufsize, "26.%u.", vendor); + + p += len; + bufsize -= len; + } + + + switch (dv_type) { + default: + case 1: + len = snprintf(p, bufsize, "%u", attr & 0xff); + p += len; + bufsize -= len; + if ((attr >> 8) == 0) return p - buffer; + break; + + case 2: + len = snprintf(p, bufsize, "%u", attr & 0xffff); + p += len; + return p - buffer; + + case 4: + len = snprintf(p, bufsize, "%u", attr); + p += len; + return p - buffer; + + } + + /* + * "attr" is a sequence of packed numbers. Unpack them. + */ + for (nest = 1; nest <= fr_attr_max_tlv; nest++) { + if (((attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]) == 0) break; + + len = snprintf(p, bufsize, ".%u", + (attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]); + + p += len; + bufsize -= len; + } + + return p - buffer; +} + +/** Free dynamically allocated (unknown attributes) + * + * If the da was dynamically allocated it will be freed, else the function + * will return without doing anything. + * + * @param da to free. + */ +void dict_attr_free(DICT_ATTR const **da) +{ + DICT_ATTR **tmp; + + if (!da || !*da) return; + + /* Don't free real DAs */ + if (!(*da)->flags.is_unknown) { + return; + } + + memcpy(&tmp, &da, sizeof(*tmp)); + talloc_free(*tmp); + + *tmp = NULL; +} + + +/** Initialises a dictionary attr for unknown attributes + * + * Initialises a dict attr for an unknown attribute/vendor/type without adding + * it to dictionary pools/hashes. + * + * @param[in,out] da struct to initialise, must be at least DICT_ATTR_SIZE bytes. + * @param[in] attr number. + * @param[in] vendor number. + * @return 0 on success. + */ +int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor) +{ + char *p; + size_t len = 0; + size_t bufsize = DICT_ATTR_MAX_NAME_LEN; + + memset(da, 0, DICT_ATTR_SIZE); + + da->attr = attr; + da->vendor = vendor; + da->type = PW_TYPE_OCTETS; + da->flags.is_unknown = true; + da->flags.is_pointer = true; + + /* + * Unknown attributes of the "WiMAX" vendor get marked up + * as being for WiMAX. + */ + if (vendor == VENDORPEC_WIMAX) { + da->flags.wimax = 1; + } + + p = da->name; + + len = snprintf(p, bufsize, "Attr-"); + p += len; + bufsize -= len; + + print_attr_oid(p, bufsize , attr, vendor); + + return 0; +} + +/** Allocs a dictionary attr for unknown attributes + * + * Allocs a dict attr for an unknown attribute/vendor/type without adding + * it to dictionary pools/hashes. + * + * @param[in] ctx to allocate DA in. + * @param[in] attr number. + * @param[in] vendor number. + * @return 0 on success. + */ +DICT_ATTR const *dict_unknown_afrom_fields(TALLOC_CTX *ctx, unsigned int attr, unsigned int vendor) +{ + uint8_t *p; + DICT_ATTR *da; + + p = talloc_zero_array(ctx, uint8_t, DICT_ATTR_SIZE); + if (!p) { + fr_strerror_printf("Out of memory"); + return NULL; + } + da = (DICT_ATTR *) p; + talloc_set_type(da, DICT_ATTR); + + if (dict_unknown_from_fields(da, attr, vendor) < 0) { + talloc_free(p); + return NULL; + } + + return da; +} + +/** Create a DICT_ATTR from an ASCII attribute and value + * + * Where the attribute name is in the form: + * - Attr-%d + * - Attr-%d.%d.%d... + * - Vendor-%d-Attr-%d + * - VendorName-Attr-%d + * + * @param[in] da to initialise. + * @param[in] name of attribute. + * @return 0 on success -1 on failure. + */ +int dict_unknown_from_str(DICT_ATTR *da, char const *name) +{ + unsigned int attr = 0, vendor = 0; + + char const *p = name; + char *q; + + if (dict_valid_name(name) < 0) return -1; + + /* + * Pull off vendor prefix first. + */ + if (strncasecmp(p, "Attr-", 5) != 0) { + if (strncasecmp(p, "Vendor-", 7) == 0) { + vendor = (int) strtol(p + 7, &q, 10); + if ((vendor == 0) || (vendor > FR_MAX_VENDOR)) { + fr_strerror_printf("Invalid vendor value in attribute name \"%s\"", name); + + return -1; + } + + p = q; + + /* must be vendor name */ + } else { + char buffer[256]; + + q = strchr(p, '-'); + + if (!q) { + fr_strerror_printf("Invalid vendor name in attribute name \"%s\"", name); + return -1; + } + + if ((size_t) (q - p) >= sizeof(buffer)) { + fr_strerror_printf("Vendor name too long in attribute name \"%s\"", name); + + return -1; + } + + memcpy(buffer, p, (q - p)); + buffer[q - p] = '\0'; + + vendor = dict_vendorbyname(buffer); + if (!vendor) { + fr_strerror_printf("Unknown name \"%s\"", name); + + return -1; + } + + p = q; + } + + if (*p != '-') { + fr_strerror_printf("Invalid text following vendor definition in attribute name \"%s\"", name); + + return -1; + } + p++; + } + + /* + * Attr-%d + */ + if (strncasecmp(p, "Attr-", 5) != 0) { + fr_strerror_printf("Unknown attribute \"%s\"", name); + + return -1; + } + + /* + * Parse the OID, with a (possibly) pre-defined vendor. + */ + if (dict_str2oid(p + 5, &attr, &vendor, 0) < 0) { + return -1; + } + + return dict_unknown_from_fields(da, attr, vendor); +} + +/** Create a DICT_ATTR from an ASCII attribute and value + * + * Where the attribute name is in the form: + * - Attr-%d + * - Attr-%d.%d.%d... + * - Vendor-%d-Attr-%d + * - VendorName-Attr-%d + * + * @param[in] ctx to alloc new attribute in. + * @param[in] name of attribute. + * @return 0 on success -1 on failure. + */ +DICT_ATTR const *dict_unknown_afrom_str(TALLOC_CTX *ctx, char const *name) +{ + uint8_t *p; + DICT_ATTR *da; + + p = talloc_zero_array(ctx, uint8_t, DICT_ATTR_SIZE); + if (!p) { + fr_strerror_printf("Out of memory"); + return NULL; + } + da = (DICT_ATTR *) p; + talloc_set_type(da, DICT_ATTR); + + if (dict_unknown_from_str(da, name) < 0) { + talloc_free(p); + return NULL; + } + + return da; +} + +/** Create a dictionary attribute by name embedded in another string + * + * Find the first invalid attribute name char in the string pointed + * to by name. + * + * Copy the characters between the start of the name string and the first + * none dict_attr_allowed_char to a buffer and initialise da as an + * unknown attribute. + * + * @param[out] da to initialise. + * @param[in,out] name string start. + * @return 0 on success or -1 on error; + */ +int dict_unknown_from_substr(DICT_ATTR *da, char const **name) +{ + char const *p; + size_t len; + char buffer[DICT_ATTR_MAX_NAME_LEN + 1]; + + if (!name || !*name) return -1; + + /* + * Advance p until we get something that's not part of + * the dictionary attribute name. + */ + for (p = *name; dict_attr_allowed_chars[(int) *p] || (*p == '.' ) || (*p == '-'); p++); + + len = p - *name; + if (len > DICT_ATTR_MAX_NAME_LEN) { + fr_strerror_printf("Attribute name too long"); + + return -1; + } + if (len == 0) { + fr_strerror_printf("Invalid attribute name"); + return -1; + } + strlcpy(buffer, *name, len + 1); + + if (dict_unknown_from_str(da, buffer) < 0) return -1; + + *name = p; + + return 0; +} + +/* + * Get an attribute by its numerical value. + */ +DICT_ATTR const *dict_attrbyvalue(unsigned int attr, unsigned int vendor) +{ + DICT_ATTR da; + + if ((attr > 0) && (attr < 256) && !vendor) return dict_base_attrs[attr]; + + da.attr = attr; + da.vendor = vendor; + + return fr_hash_table_finddata(attributes_byvalue, &da); +} + + +/** Get an attribute by its numerical value and data type + * + * Used only for COMBO_IP + * + * @return The attribute, or NULL if not found + */ +DICT_ATTR const *dict_attrbytype(unsigned int attr, unsigned int vendor, + PW_TYPE type) +{ + DICT_ATTR da; + + da.attr = attr; + da.vendor = vendor; + da.type = type; + + return fr_hash_table_finddata(attributes_combo, &da); +} + +/** Using a parent and attr/vendor, find a child attr/vendor + * + */ +int dict_attr_child(DICT_ATTR const *parent, + unsigned int *pattr, unsigned int *pvendor) +{ + unsigned int attr, vendor; + DICT_ATTR da; + + if (!parent || !pattr || !pvendor) return false; + + attr = *pattr; + vendor = *pvendor; + + /* + * Only some types can have children + */ + switch (parent->type) { + default: return false; + + case PW_TYPE_VSA: + case PW_TYPE_TLV: + case PW_TYPE_EVS: + case PW_TYPE_EXTENDED: + case PW_TYPE_LONG_EXTENDED: + break; + } + + if ((vendor == 0) && (parent->vendor != 0)) return false; + + /* + * Bootstrap by starting off with the parents values. + */ + da.attr = parent->attr; + da.vendor = parent->vendor; + + /* + * Do various butchery to insert the "attr" value. + * + * 00VID 000000AA normal VSA for vendor VID + * 00VID DDCCBBAA normal VSAs with TLVs + * EE000 000000AA extended attr (241.1) + * EE000 DDCCBBAA extended attr with TLVs + * EEVID 000000AA EVS with vendor VID, attr AAA + * EEVID DDCCBBAA EVS with TLVs + */ + if (!da.vendor) { + da.vendor = parent->attr * FR_MAX_VENDOR; + da.vendor |= vendor; + da.attr = attr; + + } else { + int i; + + /* + * Trying to nest too deep. It's an error + */ + if (parent->attr & (fr_attr_mask[MAX_TLV_NEST] << fr_attr_shift[MAX_TLV_NEST])) { + return false; + } + + for (i = MAX_TLV_NEST - 1; i >= 0; i--) { + if ((parent->attr & (fr_attr_mask[i] << fr_attr_shift[i]))) { + da.attr |= (attr & fr_attr_mask[i + 1]) << fr_attr_shift[i + 1]; + goto find; + } + } + + return false; + } + +find: +#if 0 + fprintf(stderr, "LOOKING FOR %08x %08x + %08x %08x --> %08x %08x\n", + parent->vendor, parent->attr, attr, vendor, + da.vendor, da.attr); +#endif + + *pattr = da.attr; + *pvendor = da.vendor; + return true; +} + +/* + * Get an attribute by it's numerical value, and the parent + */ +DICT_ATTR const *dict_attrbyparent(DICT_ATTR const *parent, unsigned int attr, unsigned int vendor) +{ + unsigned int my_attr, my_vendor; + DICT_ATTR da; + + my_attr = attr; + my_vendor = vendor; + + if (!dict_attr_child(parent, &my_attr, &my_vendor)) return NULL; + + da.attr = my_attr; + da.vendor = my_vendor; + + return fr_hash_table_finddata(attributes_byvalue, &da); +} + + +/* + * Get an attribute by its name. + */ +DICT_ATTR const *dict_attrbyname(char const *name) +{ + DICT_ATTR *da; + uint32_t buffer[(sizeof(*da) + DICT_ATTR_MAX_NAME_LEN + 3)/4]; + + if (!name) return NULL; + + da = (DICT_ATTR *) buffer; + strlcpy(da->name, name, DICT_ATTR_MAX_NAME_LEN + 1); + + da = fr_hash_table_finddata(attributes_byname, da); + if (!da) return NULL; + + if (!da->flags.is_dup) return da; + + /* + * This MUST exist if the dup flag is set. + */ + return dict_attrbyvalue(da->attr, da->vendor); +} + +/** Look up a dictionary attribute by name embedded in another string + * + * Find the first invalid attribute name char in the string pointed + * to by name. + * + * Copy the characters between the start of the name string and the first + * none dict_attr_allowed_char to a buffer and perform a dictionary lookup + * using that value. + * + * If the attribute exists, advance the pointer pointed to by name + * to the first none dict_attr_allowed_char char, and return the DA. + * + * If the attribute does not exist, don't advance the pointer and return + * NULL. + * + * @param[in,out] name string start. + * @return NULL if no attributes matching the name could be found, else + */ +DICT_ATTR const *dict_attrbyname_substr(char const **name) +{ + DICT_ATTR *find; + DICT_ATTR const *da; + char const *p; + size_t len; + uint32_t buffer[(sizeof(*find) + DICT_ATTR_MAX_NAME_LEN + 3)/4]; + + if (!name || !*name) return NULL; + + find = (DICT_ATTR *) buffer; + + /* + * Advance p until we get something that's not part of + * the dictionary attribute name. + */ + for (p = *name; dict_attr_allowed_chars[(int) *p]; p++); + + len = p - *name; + if (len > DICT_ATTR_MAX_NAME_LEN) { + fr_strerror_printf("Attribute name too long"); + + return NULL; + } + strlcpy(find->name, *name, len + 1); + + da = fr_hash_table_finddata(attributes_byname, find); + if (!da) { + fr_strerror_printf("Unknown attribute \"%s\"", find->name); + return NULL; + } + *name = p; + + return da; +} + +/* + * Associate a value with an attribute and return it. + */ +DICT_VALUE *dict_valbyattr(unsigned int attr, unsigned int vendor, int value) +{ + DICT_VALUE dval, *dv; + + /* + * First, look up aliases. + */ + dval.attr = attr; + dval.vendor = vendor; + dval.name[0] = '\0'; + + /* + * Look up the attribute alias target, and use + * the correct attribute number if found. + */ + dv = fr_hash_table_finddata(values_byname, &dval); + if (dv) dval.attr = dv->value; + + dval.value = value; + + return fr_hash_table_finddata(values_byvalue, &dval); +} + +/* + * Associate a value with an attribute and return it. + */ +char const *dict_valnamebyattr(unsigned int attr, unsigned int vendor, int value) +{ + DICT_VALUE *dv; + + dv = dict_valbyattr(attr, vendor, value); + if (!dv) return ""; + + return dv->name; +} + +/* + * Get a value by its name, keyed off of an attribute. + */ +DICT_VALUE *dict_valbyname(unsigned int attr, unsigned int vendor, char const *name) +{ + DICT_VALUE *my_dv, *dv; + uint32_t buffer[(sizeof(*my_dv) + DICT_VALUE_MAX_NAME_LEN + 3)/4]; + + if (!name) return NULL; + + my_dv = (DICT_VALUE *) buffer; + my_dv->attr = attr; + my_dv->vendor = vendor; + my_dv->name[0] = '\0'; + + /* + * Look up the attribute alias target, and use + * the correct attribute number if found. + */ + dv = fr_hash_table_finddata(values_byname, my_dv); + if (dv) my_dv->attr = dv->value; + + strlcpy(my_dv->name, name, DICT_VALUE_MAX_NAME_LEN + 1); + + return fr_hash_table_finddata(values_byname, my_dv); +} + +/* + * Get the vendor PEC based on the vendor name + * + * This is efficient only for small numbers of vendors. + */ +int dict_vendorbyname(char const *name) +{ + DICT_VENDOR *dv; + size_t buffer[(sizeof(*dv) + DICT_VENDOR_MAX_NAME_LEN + sizeof(size_t) - 1) / sizeof(size_t)]; + + if (!name) return 0; + + dv = (DICT_VENDOR *) buffer; + strlcpy(dv->name, name, DICT_VENDOR_MAX_NAME_LEN + 1); + + dv = fr_hash_table_finddata(vendors_byname, dv); + if (!dv) return 0; + + return dv->vendorpec; +} + +/* + * Return the vendor struct based on the PEC. + */ +DICT_VENDOR *dict_vendorbyvalue(int vendorpec) +{ + DICT_VENDOR dv; + + dv.vendorpec = vendorpec; + + return fr_hash_table_finddata(vendors_byvalue, &dv); +} + +/** Converts an unknown to a known by adding it to the internal dictionaries. + * + * Does not free old DICT_ATTR, that is left up to the caller. + * + * @param old unknown attribute to add. + * @return existing DICT_ATTR if old was found in a dictionary, else the new entry in the dictionary + * representing old. + */ +DICT_ATTR const *dict_unknown_add(DICT_ATTR const *old) +{ + DICT_ATTR const *da, *parent; + ATTR_FLAGS flags; + + if (!old) return NULL; + + if (!old->flags.is_unknown) return old; + + da = dict_attrbyvalue(old->attr, old->vendor); + if (da) return da; + + memcpy(&flags, &old->flags, sizeof(flags)); + flags.is_unknown = false; + + parent = dict_parent(old->attr, old->vendor); + if (parent) { + if (parent->flags.has_tlv) flags.is_tlv = true; + flags.evs = parent->flags.evs; + flags.extended = parent->flags.extended; + flags.long_extended = parent->flags.long_extended; + } + + if (dict_addattr(old->name, old->attr, old->vendor, old->type, flags) < 0) { + return NULL; + } + + da = dict_attrbyvalue(old->attr, old->vendor); + return da; +} + +size_t dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da) +{ + return print_attr_oid(buffer, buflen, da->attr, da->vendor); +} + +int dict_walk(fr_hash_table_walk_t callback, void *context) +{ + return fr_hash_table_walk(attributes_byname, callback, context); +} diff --git a/src/lib/event.c b/src/lib/event.c new file mode 100644 index 0000000..9eb9d1a --- /dev/null +++ b/src/lib/event.c @@ -0,0 +1,843 @@ +/* + * event.c Non-thread-safe event handling, specific to a RADIUS + * server. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2007 The FreeRADIUS server project + * Copyright 2007 Alan DeKok <aland@ox.org> + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/heap.h> +#include <freeradius-devel/event.h> + +#include <pthread.h> + +#ifdef HAVE_KQUEUE +#ifndef HAVE_SYS_EVENT_H +#error kqueue requires <sys/event.h> + +#else +#include <sys/event.h> +#endif +#endif /* HAVE_KQUEUE */ + +typedef struct fr_event_fd_t { + int fd; + + fr_event_fd_handler_t handler; + fr_event_fd_handler_t write_handler; + void *ctx; +} fr_event_fd_t; + +#define FR_EV_MAX_FDS (512) + +#undef USEC +#define USEC (1000000) + +struct fr_event_list_t { + fr_heap_t *times; + + int exit; + + fr_event_status_t status; + + struct timeval now; + bool dispatch; + + int num_readers; +#ifndef HAVE_KQUEUE + int max_readers; + int max_fd; + + fd_set read_fds; + fd_set write_fds; +#else + int kq; + struct kevent events[FR_EV_MAX_FDS]; /* so it doesn't go on the stack every time */ +#endif + + fr_event_fd_t readers[FR_EV_MAX_FDS]; +}; + +/* + * Internal structure for managing events. + */ +struct fr_event_t { + fr_event_callback_t callback; + void *ctx; + struct timeval when; + fr_event_t **parent; + int heap; +}; + + +static int fr_event_list_time_cmp(void const *one, void const *two) +{ + fr_event_t const *a = one; + fr_event_t const *b = two; + + if (a->when.tv_sec < b->when.tv_sec) return -1; + if (a->when.tv_sec > b->when.tv_sec) return +1; + + if (a->when.tv_usec < b->when.tv_usec) return -1; + if (a->when.tv_usec > b->when.tv_usec) return +1; + + return 0; +} + + +static int _event_list_free(fr_event_list_t *list) +{ + fr_event_list_t *el = list; + fr_event_t *ev; + + while ((ev = fr_heap_peek(el->times)) != NULL) { + fr_event_delete(el, &ev); + } + + fr_heap_delete(el->times); + +#ifdef HAVE_KQUEUE + close(el->kq); +#endif + + return 0; +} + + +fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status) +{ + int i; + fr_event_list_t *el; + + el = talloc_zero(ctx, fr_event_list_t); + if (!fr_assert(el)) { + return NULL; + } + talloc_set_destructor(el, _event_list_free); + + el->times = fr_heap_create(fr_event_list_time_cmp, offsetof(fr_event_t, heap)); + if (!el->times) { + talloc_free(el); + return NULL; + } + + for (i = 0; i < FR_EV_MAX_FDS; i++) { + el->readers[i].fd = -1; + } + +#ifndef HAVE_KQUEUE + el->max_fd = 0; + FD_ZERO(&el->read_fds); + FD_ZERO(&el->write_fds); +#else + el->kq = kqueue(); + if (el->kq < 0) { + talloc_free(el); + return NULL; + } +#endif + + el->status = status; + + return el; +} + +int fr_event_list_num_fds(fr_event_list_t *el) +{ + if (!el) return 0; + + return el->num_readers; +} + +int fr_event_list_num_elements(fr_event_list_t *el) +{ + if (!el) return 0; + + return fr_heap_num_elements(el->times); +} + + +int fr_event_delete(fr_event_list_t *el, fr_event_t **parent) +{ + int ret; + + fr_event_t *ev; + + if (!el || !parent || !*parent) return 0; + +#ifndef NDEBUG + /* + * Validate the event_t struct to detect memory issues early. + */ + ev = talloc_get_type_abort(*parent, fr_event_t); + +#else + ev = *parent; +#endif + + if (ev->parent) { + fr_assert(*(ev->parent) == ev); + *ev->parent = NULL; + } + *parent = NULL; + + ret = fr_heap_extract(el->times, ev); + fr_assert(ret == 1); /* events MUST be in the heap */ + talloc_free(ev); + + return ret; +} + + +int fr_event_insert(fr_event_list_t *el, fr_event_callback_t callback, void *ctx, struct timeval *when, + fr_event_t **parent) +{ + fr_event_t *ev; + + if (!el) { + fr_strerror_printf("Invalid arguments (NULL event list)"); + return 0; + } + + if (!callback) { + fr_strerror_printf("Invalid arguments (NULL callback)"); + return 0; + } + + if (!when || (when->tv_usec >= USEC)) { + fr_strerror_printf("Invalid arguments (time)"); + return 0; + } + + if (!parent) { + fr_strerror_printf("Invalid arguments (NULL parent)"); + return 0; + } + + /* + * If there is an event, re-use it instead of freeing it + * and allocating a new one. + */ + if (*parent) { + int ret; + +#ifndef NDEBUG + ev = talloc_get_type_abort(*parent, fr_event_t); +#else + ev = *parent; +#endif + + ret = fr_heap_extract(el->times, ev); + fr_assert(ret == 1); /* events MUST be in the heap */ + + memset(ev, 0, sizeof(*ev)); + } else { + ev = talloc_zero(el, fr_event_t); + if (!ev) return 0; + } + + ev->callback = callback; + ev->ctx = ctx; + ev->when = *when; + ev->parent = parent; + + if (!fr_heap_insert(el->times, ev)) { + talloc_free(ev); + return 0; + } + + *parent = ev; + return 1; +} + + +int fr_event_run(fr_event_list_t *el, struct timeval *when) +{ + fr_event_callback_t callback; + void *ctx; + fr_event_t *ev; + + if (!el) return 0; + + if (fr_heap_num_elements(el->times) == 0) { + when->tv_sec = 0; + when->tv_usec = 0; + return 0; + } + + ev = fr_heap_peek(el->times); + if (!ev) { + when->tv_sec = 0; + when->tv_usec = 0; + return 0; + } + +#ifndef NDEBUG + ev = talloc_get_type_abort(ev, fr_event_t); +#endif + + /* + * See if it's time to do this one. + */ + if ((ev->when.tv_sec > when->tv_sec) || + ((ev->when.tv_sec == when->tv_sec) && + (ev->when.tv_usec > when->tv_usec))) { + *when = ev->when; + return 0; + } + + callback = ev->callback; + ctx = ev->ctx; + + /* + * Delete the event before calling it. + */ + fr_event_delete(el, ev->parent); + + callback(ctx); + return 1; +} + + +int fr_event_now(fr_event_list_t *el, struct timeval *when) +{ + if (!when) return 0; + + if (el && el->dispatch) { + *when = el->now; + } else { + gettimeofday(when, NULL); + } + + return 1; +} + + +int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, + fr_event_fd_handler_t handler, void *ctx) +{ + int i; + fr_event_fd_t *ef; + + if (!el) { + fr_strerror_printf("Invalid arguments (NULL event list)"); + return 0; + } + + if (!handler) { + fr_strerror_printf("Invalid arguments (NULL handler)"); + return 0; + } + + if (!ctx) { + fr_strerror_printf("Invalid arguments (NULL ctx)"); + return 0; + } + + if (fd < 0) { + fr_strerror_printf("Invalid arguments (bad FD %i)", fd); + return 0; + } + + if (type != 0) { + fr_strerror_printf("Invalid type %i", type); + return 0; + } + + if (el->num_readers >= FR_EV_MAX_FDS) { + fr_strerror_printf("Too many readers"); + return 0; + } + ef = NULL; + +#ifdef HAVE_KQUEUE + /* + * We need to store TWO fields with the event. kqueue + * only lets us store one. If we put the two fields into + * a malloc'd structure, that would help. Except that + * kqueue can silently delete the event when the socket + * is closed, and not give us the opportunity to free it. + * <sigh> + * + * The solution is to put the fields into an array, and + * do a linear search on addition/deletion of the FDs. + * However, to avoid MOST linear issues, we start off the + * search at "FD" offset. Since FDs are unique, AND + * usually less than 256, we do "FD & 0xff", which is a + * good guess, and makes the lookups mostly O(1). + */ + for (i = 0; i < FR_EV_MAX_FDS; i++) { + int j; + struct kevent evset; + + j = (i + fd) & (FR_EV_MAX_FDS - 1); + + if (el->readers[j].fd >= 0) continue; + + /* + * We want to read from the FD. + */ + EV_SET(&evset, fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, &el->readers[j]); + if (kevent(el->kq, &evset, 1, NULL, 0, NULL) < 0) { + fr_strerror_printf("Failed inserting event for FD %i: %s", fd, fr_syserror(errno)); + return 0; + } + + ef = &el->readers[j]; + el->num_readers++; + break; + } + +#else /* HAVE_KQUEUE */ + + /* + * select() has limits. + */ + if (fd > FD_SETSIZE) { + fprintf(stderr, "FD is larger than FD_SETSIZE"); + return 0; + } + + for (i = 0; i <= el->max_readers; i++) { + /* + * Be fail-safe on multiple inserts. + */ + if (el->readers[i].fd == fd) { + if ((el->readers[i].handler != handler) || + (el->readers[i].ctx != ctx)) { + fr_strerror_printf("Multiple handlers for same FD"); + return 0; + } + + /* + * No change. + */ + return 1; + } + + if (el->readers[i].fd < 0) { + ef = &el->readers[i]; + el->num_readers++; + + if (i == el->max_readers) el->max_readers = i + 1; + + FD_SET(fd, &el->read_fds); + if (el->max_fd <= fd) el->max_fd = fd; + break; + } + } +#endif + + if (!ef) { + fr_strerror_printf("Failed assigning FD"); + return 0; + } + + ef->fd = fd; + ef->handler = handler; + ef->ctx = ctx; + + return 1; +} + +int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, + fr_event_fd_handler_t write_handler, void *ctx) +{ + int i; + + if (!el || (fd < 0)) return 0; + + if (type != 0) return 0; + +#ifdef HAVE_KQUEUE + for (i = 0; i < FR_EV_MAX_FDS; i++) { + int j; + struct kevent evset; + + j = (i + fd) & (FR_EV_MAX_FDS - 1); + + if (el->readers[j].fd != fd) continue; + + fr_assert(ctx = el->readers[j].ctx); + + /* + * Tell us when the socket is ready for writing + */ + if (write_handler) { + fr_assert(!el->readers[j].write_handler); + + el->readers[j].write_handler = write_handler; + + EV_SET(&evset, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &el->readers[j]); + } else { + fr_assert(el->readers[j].write_handler); + + el->readers[j].write_handler = NULL; + + EV_SET(&evset, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + } + if (kevent(el->kq, &evset, 1, NULL, 0, NULL) < 0) { + fr_strerror_printf("Failed inserting event for FD %i: %s", fd, fr_syserror(errno)); + return 0; + } + + return 1; + } + +#else + + for (i = 0; i < el->max_readers; i++) { + if (el->readers[i].fd != fd) continue; + + fr_assert(ctx = el->readers[i].ctx); + el->readers[i].write_handler = write_handler; + + FD_SET(fd, &el->write_fds); /* fd MUST already be in the set of readers! */ + return 1; + } +#endif /* HAVE_KQUEUE */ + + return 0; +} + +int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) +{ + int i; + + if (!el || (fd < 0)) return 0; + + if (type != 0) return 0; + +#ifdef HAVE_KQUEUE + for (i = 0; i < FR_EV_MAX_FDS; i++) { + int j; + struct kevent evset; + + j = (i + fd) & (FR_EV_MAX_FDS - 1); + + if (el->readers[j].fd != fd) continue; + + /* + * Tell the kernel to delete it from the list. + * + * The caller MAY have closed it, in which case + * the kernel has removed it from the list. So + * we ignore the return code from kevent(). + */ + EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); + + /* + * Delete the write handler if it exits. + */ + if (el->readers[j].write_handler) { + EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); + } + + el->readers[j].fd = -1; + el->num_readers--; + + return 1; + } + +#else + for (i = 0; i < el->max_readers; i++) { + if (el->readers[i].fd == fd) { + el->readers[i].fd = -1; + el->num_readers--; + + if ((i + 1) == el->max_readers) el->max_readers = i; + FD_CLR(fd, &el->read_fds); + FD_CLR(fd, &el->write_fds); + + /* + * @todo - update el->max_fd, too. + */ + return 1; + } + } +#endif /* HAVE_KQUEUE */ + + return 0; +} + + +void fr_event_loop_exit(fr_event_list_t *el, int code) +{ + if (!el) return; + + el->exit = code; +} + +bool fr_event_loop_exiting(fr_event_list_t *el) +{ + return (el->exit != 0); +} + +int fr_event_loop(fr_event_list_t *el) +{ + int i, rcode; + struct timeval when, *wake; +#ifdef HAVE_KQUEUE + struct timespec ts_when, *ts_wake; +#else + fd_set read_fds, write_fds; +#endif + + el->exit = 0; + el->dispatch = true; + + while (!el->exit) { + /* + * Find the first event. If there's none, we wait + * on the socket forever. + */ + when.tv_sec = 0; + when.tv_usec = 0; + + if (fr_heap_num_elements(el->times) > 0) { + fr_event_t *ev; + + ev = fr_heap_peek(el->times); + if (!ev) { + fr_exit_now(42); + } + + gettimeofday(&el->now, NULL); + + if (timercmp(&el->now, &ev->when, <)) { + when = ev->when; + when.tv_sec -= el->now.tv_sec; + + if (when.tv_sec > 0) { + when.tv_sec--; + when.tv_usec += USEC; + } else { + when.tv_sec = 0; + } + when.tv_usec -= el->now.tv_usec; + if (when.tv_usec >= USEC) { + when.tv_usec -= USEC; + when.tv_sec++; + } + } else { /* we've passed the event time */ + when.tv_sec = 0; + when.tv_usec = 0; + } + + wake = &when; + } else { + wake = NULL; + } + + /* + * Tell someone what the status is. + */ + if (el->status) el->status(wake); + +#ifndef HAVE_KQUEUE + read_fds = el->read_fds; + write_fds = el->write_fds; + rcode = select(el->max_fd + 1, &read_fds, &write_fds, NULL, wake); + if ((rcode < 0) && (errno != EINTR)) { + fr_strerror_printf("Failed in select: %s", fr_syserror(errno)); + el->dispatch = false; + return -1; + } + +#else /* HAVE_KQUEUE */ + + if (wake) { + ts_wake = &ts_when; + ts_when.tv_sec = when.tv_sec; + ts_when.tv_nsec = when.tv_usec * 1000; + + } else { + ts_wake = NULL; + } + + rcode = kevent(el->kq, NULL, 0, el->events, FR_EV_MAX_FDS, ts_wake); +#endif /* HAVE_KQUEUE */ + + if (fr_heap_num_elements(el->times) > 0) { + do { + gettimeofday(&el->now, NULL); + when = el->now; + } while (fr_event_run(el, &when) == 1); + } + + if (rcode <= 0) continue; + +#ifndef HAVE_KQUEUE + /* + * Loop over all of the sockets to see if there's + * an event for that socket. + */ + for (i = 0; i < el->max_readers; i++) { + fr_event_fd_t *ef = &el->readers[i]; + + if (ef->fd < 0) continue; + + /* + * Check if the socket is available for writing. + */ + if (ef->write_handler && FD_ISSET(ef->fd, &write_fds)) { + ef->write_handler(el, ef->fd, ef->ctx); + } + + if (!FD_ISSET(ef->fd, &read_fds)) continue; + + ef->handler(el, ef->fd, ef->ctx); + } + +#else /* HAVE_KQUEUE */ + + /* + * Loop over all of the events, servicing them. + */ + for (i = 0; i < rcode; i++) { + fr_event_fd_t *ef = el->events[i].udata; + + if (el->events[i].flags & EV_EOF) { + /* + * FIXME: delete the handler + * here, and fix process.c to not + * call fr_event_fd_delete(). + * It's cleaner. + * + * Call the handler, which SHOULD + * delete the connection. + */ + ef->handler(el, ef->fd, ef->ctx); + continue; + } + + if (el->events[i].filter == EVFILT_WRITE) { + ef->write_handler(el, ef->fd, ef->ctx); + continue; + } + + /* + * Else it's our event. We only set + * EVFILT_READ, so it must be a read + * event. + */ + ef->handler(el, ef->fd, ef->ctx); + } +#endif /* HAVE_KQUEUE */ + } + + el->dispatch = false; + return el->exit; +} + + +#ifdef TESTING + +/* + * cc -g -I .. -c rbtree.c -o rbtree.o && cc -g -I .. -c isaac.c -o isaac.o && cc -DTESTING -I .. -c event.c -o event_mine.o && cc event_mine.o rbtree.o isaac.o -o event + * + * ./event + * + * And hit CTRL-S to stop the output, CTRL-Q to continue. + * It normally alternates printing the time and sleeping, + * but when you hit CTRL-S/CTRL-Q, you should see a number + * of events run right after each other. + * + * OR + * + * valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./event + */ + +static void print_time(void *ctx) +{ + struct timeval *when = ctx; + + printf("%d.%06d\n", when->tv_sec, when->tv_usec); + fflush(stdout); +} + +static fr_randctx rand_pool; + +static uint32_t event_rand(void) +{ + uint32_t num; + + num = rand_pool.randrsl[rand_pool.randcnt++ & 0xff]; + if (rand_pool.randcnt == 256) { + fr_isaac(&rand_pool); + rand_pool.randcnt = 0; + } + + return num; +} + + +#define MAX 100 +int main(int argc, char **argv) +{ + int i, rcode; + struct timeval array[MAX]; + struct timeval now, when; + fr_event_list_t *el; + + el = fr_event_list_create(NULL, NULL); + if (!el) exit(1); + + memset(&rand_pool, 0, sizeof(rand_pool)); + rand_pool.randrsl[1] = time(NULL); + + fr_randinit(&rand_pool, 1); + rand_pool.randcnt = 0; + + gettimeofday(&array[0], NULL); + for (i = 1; i < MAX; i++) { + array[i] = array[i - 1]; + + array[i].tv_usec += event_rand() & 0xffff; + if (array[i].tv_usec > 1000000) { + array[i].tv_usec -= 1000000; + array[i].tv_sec++; + } + fr_event_insert(el, print_time, &array[i], &array[i]); + } + + while (fr_event_list_num_elements(el)) { + gettimeofday(&now, NULL); + when = now; + if (!fr_event_run(el, &when)) { + int delay = (when.tv_sec - now.tv_sec) * 1000000; + delay += when.tv_usec; + delay -= now.tv_usec; + + printf("\tsleep %d\n", delay); + fflush(stdout); + usleep(delay); + } + } + + talloc_free(el); + + return 0; +} +#endif diff --git a/src/lib/fifo.c b/src/lib/fifo.c new file mode 100644 index 0000000..7a9ecfa --- /dev/null +++ b/src/lib/fifo.c @@ -0,0 +1,197 @@ +/* + * fifo.c Non-thread-safe fifo (FIFO) implementation, based + * on hash tables. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2005,2006 The FreeRADIUS server project + * Copyright 2005 Alan DeKok <aland@ox.org> + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +struct fr_fifo_t { + unsigned int num; + unsigned int first, last; + unsigned int max; + fr_fifo_free_t freeNode; + + void *data[1]; +}; + + +fr_fifo_t *fr_fifo_create(TALLOC_CTX *ctx, int max, fr_fifo_free_t freeNode) +{ + fr_fifo_t *fi; + + if ((max < 2) || (max > (1024 * 1024))) return NULL; + + fi = talloc_zero_size(ctx, (sizeof(*fi) + (sizeof(fi->data[0])*max))); + if (!fi) return NULL; + talloc_set_type(fi, fr_fifo_t); + + fi->max = max; + fi->freeNode = freeNode; + + return fi; +} + +void fr_fifo_free(fr_fifo_t *fi) +{ + unsigned int i; + + if (!fi) return; + + if (fi->freeNode) { + for (i = 0 ; i < fi->num; i++) { + unsigned int element; + + element = i + fi->first; + if (element > fi->max) { + element -= fi->max; + } + + fi->freeNode(fi->data[element]); + fi->data[element] = NULL; + } + } + + memset(fi, 0, sizeof(*fi)); + talloc_free(fi); +} + +int fr_fifo_push(fr_fifo_t *fi, void *data) +{ + if (!fi || !data) return 0; + + if (fi->num >= fi->max) return 0; + + fi->data[fi->last++] = data; + if (fi->last >= fi->max) fi->last = 0; + fi->num++; + + return 1; +} + +void *fr_fifo_pop(fr_fifo_t *fi) +{ + void *data; + + if (!fi || (fi->num == 0)) return NULL; + + data = fi->data[fi->first++]; + + if (fi->first >= fi->max) { + fi->first = 0; + } + fi->num--; + + return data; +} + +void *fr_fifo_peek(fr_fifo_t *fi) +{ + if (!fi || (fi->num == 0)) return NULL; + + return fi->data[fi->first]; +} + +unsigned int fr_fifo_num_elements(fr_fifo_t *fi) +{ + if (!fi) return 0; + + return fi->num; +} + +#ifdef TESTING + +/* + * cc -DTESTING -I .. fifo.c -o fifo + * + * ./fifo + */ + +#define MAX 1024 +int main(int argc, char **argv) +{ + int i, j, array[MAX]; + fr_fifo_t *fi; + + fi = fr_fifo_create(NULL, MAX, NULL); + if (!fi) fr_exit(1); + + for (j = 0; j < 5; j++) { +#define SPLIT (MAX/3) +#define COUNT ((j * SPLIT) + i) + for (i = 0; i < SPLIT; i++) { + array[COUNT % MAX] = COUNT; + + if (!fr_fifo_push(fi, &array[COUNT % MAX])) { + fprintf(stderr, "%d %d\tfailed pushing %d\n", + j, i, COUNT); + fr_exit(2); + } + + if (fr_fifo_num_elements(fi) != (i + 1)) { + fprintf(stderr, "%d %d\tgot size %d expected %d\n", + j, i, i + 1, fr_fifo_num_elements(fi)); + fr_exit(1); + } + } + + if (fr_fifo_num_elements(fi) != SPLIT) { + fprintf(stderr, "HALF %d %d\n", + fr_fifo_num_elements(fi), SPLIT); + fr_exit(1); + } + + for (i = 0; i < SPLIT; i++) { + int *p; + + p = fr_fifo_pop(fi); + if (!p) { + fprintf(stderr, "No pop at %d\n", i); + fr_exit(3); + } + + if (*p != COUNT) { + fprintf(stderr, "%d %d\tgot %d expected %d\n", + j, i, *p, COUNT); + fr_exit(4); + } + + if (fr_fifo_num_elements(fi) != SPLIT - (i + 1)) { + fprintf(stderr, "%d %d\tgot size %d expected %d\n", + j, i, SPLIT - (i + 1), fr_fifo_num_elements(fi)); + fr_exit(1); + } + } + + if (fr_fifo_num_elements(fi) != 0) { + fprintf(stderr, "ZERO %d %d\n", + fr_fifo_num_elements(fi), 0); + fr_exit(1); + } + } + + fr_fifo_free(fi); + + fr_exit(0); +} +#endif diff --git a/src/lib/filters.c b/src/lib/filters.c new file mode 100644 index 0000000..3f3b63d --- /dev/null +++ b/src/lib/filters.c @@ -0,0 +1,1253 @@ +/* + * filters.c Routines to parse Ascend's filter attributes. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2003,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#ifdef WITH_ASCEND_BINARY +#include <ctype.h> + +/* + * Two types of filters are supported, GENERIC and IP. The identifiers + * are: + */ + +#define RAD_FILTER_GENERIC 0 +#define RAD_FILTER_IP 1 +#define RAD_FILTER_IPX 2 + +/* + * Generic filters mask and match up to RAD_MAX_FILTER_LEN bytes + * starting at some offset. The length is: + */ +#define RAD_MAX_FILTER_LEN 6 + +/* + * ASCEND extensions for ABINARY filters + */ + +#define IPX_NODE_ADDR_LEN 6 + +#if ! defined( false ) +# define false 0 +# define true (! false) +#endif + + +/* + * ascend_ip_filter_t + * + * The binary format of an IP filter. ALL fields are stored in + * network byte order. + * + * srcip: The source IP address. + * + * dstip: The destination IP address. + * + * srcmask: The number of leading one bits in the source address + * mask. Specifies the bits of interest. + * + * dstmask: The number of leading one bits in the destination + * address mask. Specifies the bits of interest. + * + * proto: The IP protocol number + * + * established: A boolean value. true when we care about the + * established state of a TCP connection. false when + * we dont care. + * + * srcport: TCP or UDP source port number. + * + * dstport: TCP or UDP destination port number. + * + * srcPortCmp: One of the values of the RadFilterComparison + * enumeration, specifying how to compare the + * srcport value. + * + * dstPortCmp: One of the values of the RadFilterComparison + * enumeration, specifying how to compare the + * dstport value. + * + * fill: Round things out to a int16_t boundary. + */ +typedef struct ascend_ip_filter_t { + uint32_t srcip; + uint32_t dstip; + uint8_t srcmask; + uint8_t dstmask; + uint8_t proto; + uint8_t established; + uint16_t srcport; + uint16_t dstport; + uint8_t srcPortComp; + uint8_t dstPortComp; + unsigned char fill[4]; /* used to be fill[2] */ +} ascend_ip_filter_t; + + +/* + * ascend_ipx_net_t + * + * net: IPX Net address + * + * node: IPX Node address + * + * socket: IPX socket address + */ +typedef struct ascend_ipx_net_t { + uint32_t net; + uint8_t node[IPX_NODE_ADDR_LEN]; + uint16_t socket; +} ascend_ipx_net_t; + +/* + * ascend_ipx_filter_t + * + * The binary format of an IPX filter. ALL fields are stored in + * network byte order. + * + * src: Source net, node, and socket. + * + * dst: Destination net, node, and socket. + * + * srcSocComp: Source socket compare value + * + * dstSocComp: Destination socket compare value + */ +typedef struct ascend_ipx_filter_t { + ascend_ipx_net_t src; + ascend_ipx_net_t dst; + uint8_t srcSocComp; + uint8_t dstSocComp; +} ascend_ipx_filter_t; + + +/* + * ascend_generic_filter_t + * + * The binary format of a GENERIC filter. ALL fields are stored in + * network byte order. + * + * offset: Number of bytes into packet to start comparison. + * + * len: Number of bytes to mask and compare. May not + * exceed RAD_MAX_FILTER_LEN. + * + * more: Boolean. If non-zero the next filter entry is + * also to be applied to a packet. + * + * mask: A bit mask specifying the bits to compare. + * + * value: A value to compare against the masked bits at + * offset in a users packet. + * + * compNeq: Defines type of comarison (Equal or Notequal) + * default is Equal. + * + * fill: Round things out to a dword boundary + */ +typedef struct ascend_generic_filter_t { + uint16_t offset; + uint16_t len; + uint16_t more; + uint8_t mask[ RAD_MAX_FILTER_LEN ]; + uint8_t value[ RAD_MAX_FILTER_LEN ]; + uint8_t compNeq; + uint8_t fill[3]; /* used to be fill[1] */ +} ascend_generic_filter_t; + +/* + * ascend_filter_t + * + * A binary filter element. Contains one of ascend_ip_filter_t, + * ascend_ipx_filter_t, or ascend_generic_filter_t. + * + * All fields are stored in network byte order. + * + * type: Either RAD_FILTER_GENERIC or RAD_FILTER_IP. + * + * forward: true if we should forward packets that match this + * filter, false if we should drop packets that match + * this filter. + * + * direction: true if this is an input filter, false if this is + * an output filter. + * + * fill: Round things out to a dword boundary. + * + * u: A union of + * ip: An ip filter entry + * generic: A generic filter entry + */ +typedef struct ascend_filter_t { + uint8_t type; + uint8_t forward; + uint8_t direction; + uint8_t fill; + union { + ascend_ip_filter_t ip; + ascend_ipx_filter_t ipx; + ascend_generic_filter_t generic; + uint8_t data[28]; /* ensure it's 32 bytes */ + } u; +} ascend_filter_t; + +/* + * This is a wild C hack... + */ +typedef struct _cpp_hack { + char data[(sizeof(ascend_filter_t) == 32) ? 1 : -1 ]; +} _cpp_hack; + +/* + * FilterPortType: + * + * Ascii names of some well known tcp/udp services. + * Used for filtering on a port type. + * + * ??? What the heck is wrong with getservbyname? + */ +static const FR_NAME_NUMBER filterPortType[] = { + { "ftp-data", 20 }, + { "ftp", 21 }, + { "telnet", 23 }, + { "smtp", 25 }, + { "nameserver", 42 }, + { "domain", 53 }, + { "tftp", 69 }, + { "gopher", 70 }, + { "finger", 79 }, + { "www", 80 }, + { "kerberos", 88 }, + { "hostname", 101 }, + { "nntp", 119 }, + { "ntp", 123 }, + { "exec", 512 }, + { "login", 513 }, + { "cmd", 514 }, + { "talk", 517 }, + { NULL , 0}, +}; + +static const FR_NAME_NUMBER filterType[] = { + { "generic", RAD_FILTER_GENERIC}, + { "ip", RAD_FILTER_IP}, + { "ipx", RAD_FILTER_IPX}, + { NULL, 0}, +}; + +typedef enum { + FILTER_GENERIC_TYPE, + FILTER_IP_TYPE, + FILTER_IN, + FILTER_OUT, + FILTER_FORWARD, + FILTER_DROP, + FILTER_GENERIC_OFFSET, + FILTER_GENERIC_MASK, + FILTER_GENERIC_VALUE, + FILTER_GENERIC_COMPNEQ, + FILTER_GENERIC_COMPEQ, + FILTER_MORE, + FILTER_IP_DST, + FILTER_IP_SRC, + FILTER_IP_PROTO, + FILTER_IP_DST_PORT, + FILTER_IP_SRC_PORT, + FILTER_EST, + FILTER_IPX_TYPE, + FILTER_IPX_DST_IPXNET, + FILTER_IPX_DST_IPXNODE, + FILTER_IPX_DST_IPXSOCK, + FILTER_IPX_SRC_IPXNET, + FILTER_IPX_SRC_IPXNODE, + FILTER_IPX_SRC_IPXSOCK +} FilterTokens; + + +static const FR_NAME_NUMBER filterKeywords[] = { + { "ip", FILTER_IP_TYPE }, + { "generic", FILTER_GENERIC_TYPE }, + { "in", FILTER_IN }, + { "out", FILTER_OUT }, + { "forward", FILTER_FORWARD }, + { "drop", FILTER_DROP }, + { "dstip", FILTER_IP_DST }, + { "srcip", FILTER_IP_SRC }, + { "dstport", FILTER_IP_DST_PORT }, + { "srcport", FILTER_IP_SRC_PORT }, + { "est", FILTER_EST }, + { "more", FILTER_MORE }, + { "!=", FILTER_GENERIC_COMPNEQ }, + { "==", FILTER_GENERIC_COMPEQ }, + { "ipx", FILTER_IPX_TYPE }, + { "dstipxnet", FILTER_IPX_DST_IPXNET }, + { "dstipxnode", FILTER_IPX_DST_IPXNODE }, + { "dstipxsock", FILTER_IPX_DST_IPXSOCK }, + { "srcipxnet", FILTER_IPX_SRC_IPXNET }, + { "srcipxnode", FILTER_IPX_SRC_IPXNODE }, + { "srcipxsock", FILTER_IPX_SRC_IPXSOCK }, + { NULL , -1}, +}; + +/* + * FilterProtoName: + * + * Ascii name of protocols used for filtering. + * + * ??? What the heck is wrong with getprotobyname? + */ +static const FR_NAME_NUMBER filterProtoName[] = { + { "tcp", 6 }, + { "udp", 17 }, + { "ospf", 89 }, + { "icmp", 1 }, + { "0", 0 }, + { NULL , -1 }, +}; + + +/* + * RadFilterComparison: + * + * An enumerated values for the IP filter port comparisons. + */ +typedef enum { + RAD_NO_COMPARE = 0, + RAD_COMPARE_LESS, + RAD_COMPARE_EQUAL, + RAD_COMPARE_GREATER, + RAD_COMPARE_NOT_EQUAL +} RadFilterComparison; + +static const FR_NAME_NUMBER filterCompare[] = { + { "<", RAD_COMPARE_LESS }, + { "=", RAD_COMPARE_EQUAL }, + { ">", RAD_COMPARE_GREATER }, + { "!=", RAD_COMPARE_NOT_EQUAL }, + { NULL, 0 }, +}; + + +/* + * ascend_parse_ipx_net + * + * srcipxnet nnnn srcipxnode mmmmm [srcipxsoc cmd value ] + */ +static int ascend_parse_ipx_net(int argc, char **argv, + ascend_ipx_net_t *net, uint8_t *comp) +{ + int token; + char const *p; + + if (argc < 3) return -1; + + /* + * Parse the net, which is a hex number. + */ + net->net = htonl(strtol(argv[0], NULL, 16)); + + /* + * Parse the node. + */ + token = fr_str2int(filterKeywords, argv[1], -1); + switch (token) { + case FILTER_IPX_SRC_IPXNODE: + case FILTER_IPX_DST_IPXNODE: + break; + + default: + return -1; + } + + /* + * Can have a leading "0x" or "0X" + */ + p = argv[2]; + if ((memcmp(p, "0X", 2) == 0) || + (memcmp(p, "0x", 2) == 0)) p += 2; + + /* + * Node must be 6 octets long. + */ + token = fr_hex2bin(net->node, IPX_NODE_ADDR_LEN, p, strlen(p)); + if (token != IPX_NODE_ADDR_LEN) return -1; + + /* + * Nothing more, die. + */ + if (argc == 3) return 3; + + /* + * Can't be too little or too much. + */ + if (argc != 6) return -1; + + /* + * Parse the socket. + */ + token = fr_str2int(filterKeywords, argv[3], -1); + switch (token) { + case FILTER_IPX_SRC_IPXSOCK: + case FILTER_IPX_DST_IPXSOCK: + break; + + default: + return -1; + } + + /* + * Parse the command "<", ">", "=" or "!=" + */ + token = fr_str2int(filterCompare, argv[4], -1); + switch (token) { + case RAD_COMPARE_LESS: + case RAD_COMPARE_EQUAL: + case RAD_COMPARE_GREATER: + case RAD_COMPARE_NOT_EQUAL: + *comp = token; + break; + + default: + return -1; + } + + /* + * Parse the value. + */ + token = strtoul(argv[5], NULL, 16); + if (token > 65535) return -1; + + net->socket = token; + net->socket = htons(net->socket); + + + /* + * Everything's OK, we parsed 6 entries. + */ + return 6; +} + +/* + * ascend_parse_ipx_filter + * + * This routine parses an IPX filter string from a string. + * The format of the string is: + * + * [ srcipxnet nnnn srcipxnode mmmmm [srcipxsoc cmd value ]] + * [ dstipxnet nnnn dstipxnode mmmmm [dstipxsoc cmd value ]] + * + * Fields in [...] are optional. + * where: + * + * srcipxnet: Keyword for source IPX address. + * nnnn = IPX Node address. + * + * srcipxnode: Keyword for source IPX Node address. + * mmmmm = IPX Node Address, could be FFFFFF. + * A vlid ipx node number should accompany ipx net number. + * + * srcipxsoc: Keyword for source IPX socket address. + * + * cmd: One of ">" or "<" or "=" or "!=". + * + * value: Socket value to be compared against, in hex. + * + * dstipxnet: Keyword for destination IPX address. + * nnnn = IPX Node address. + * + * dstipxnode: Keyword for destination IPX Node address. + * mmmmm = IPX Node Address, could be FFFFFF. + * A valid ipx node number should accompany ipx net number. + * + * dstipxsoc: Keyword for destination IPX socket address. + * + * cmd: One of ">" or "<" or "=" or "!=". + * + * value: Socket value to be compared against, in hex. + */ +static int ascend_parse_ipx(int argc, char **argv, ascend_ipx_filter_t *filter) +{ + int rcode; + int token; + int flags = 0; + + /* + * We may have nothing, in which case we simply return. + */ + if (argc == 0) return 0; + + /* + * Must have "net N node M" + */ + if (argc < 4) return -1; + + while ((argc > 0) && (flags != 0x03)) { + token = fr_str2int(filterKeywords, argv[0], -1); + switch (token) { + case FILTER_IPX_SRC_IPXNET: + if (flags & 0x01) return -1; + rcode = ascend_parse_ipx_net(argc - 1, argv + 1, + &(filter->src), + &(filter->srcSocComp)); + if (rcode < 0) return -1; + argc -= (rcode + 1); + argv += rcode + 1; + flags |= 0x01; + break; + + case FILTER_IPX_DST_IPXNET: + if (flags & 0x02) return -1; + rcode = ascend_parse_ipx_net(argc - 1, argv + 1, + &(filter->dst), + &(filter->dstSocComp)); + if (rcode < 0) return -1; + argc -= (rcode + 1); + argv += rcode + 1; + flags |= 0x02; + break; + + default: + fr_strerror_printf("Unknown string \"%s\" in IPX data filter", + argv[0]); + return -1; + } + } + + /* + * Arguments left over: die. + */ + if (argc != 0) return -1; + + /* + * Everything's OK. + */ + return 0; +} + + +/* + * Parse an IP address and optionally a netmask, to a uint32_t. + * + * ipaddr should already be initialized to zero. + * ipaddr is in network byte order. + * + * Returns -1 on error, or the number of bits in the netmask, otherwise. + */ +static int ascend_parse_ipaddr(uint32_t *ipaddr, char *str) +{ + int count = 0; + int ip[4]; + int masklen; + uint32_t netmask = 0; + + /* + * Look for IP's. + */ + count = 0; + while (*str && (count < 4) && (netmask == 0)) { + next: + ip[count] = 0; + + while (*str) { + switch (*str) { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': + ip[count] *= 10; + ip[count] += (*str) - '0'; + str++; + break; + + + case '.': /* dot between IP numbers. */ + str++; + if (ip[count] > 255) return -1; + + /* + * 24, 16, 8, 0, done. + */ + *ipaddr |= (ip[count] << (8 * (3 - count))); + count++; + goto next; + + case '/': /* netmask */ + str++; + masklen = atoi(str); + if ((masklen < 0) || (masklen > 32)) return -1; + str += strspn(str, "0123456789"); + netmask = masklen; + goto finalize; + + default: + fr_strerror_printf("Invalid character in IP address"); + return -1; + } + } /* loop over one character */ + } /* loop until the count hits 4 */ + + if (count == 3) { + finalize: + /* + * Do the last one, too. + */ + if (ip[count] > 255) return -1; + + /* + * 24, 16, 8, 0, done. + */ + *ipaddr |= (ip[count] << (8 * (3 - count))); + } + + /* + * We've hit the end of the IP address, and there's something + * else left over: die. + */ + if (*str) return -1; + + /* + * Set the default netmask. + */ + if (!netmask) { + if (!*ipaddr) { + netmask = 0; + } else if ((*ipaddr & 0x80000000) == 0) { + netmask = 8; + } else if ((*ipaddr & 0xc0000000) == 0x80000000) { + netmask = 16; + } else if ((*ipaddr & 0xe0000000) == 0xc0000000) { + netmask = 24; + } else { + netmask = 32; + } + } + + *ipaddr = htonl(*ipaddr); + return netmask; +} + +/* + * ascend_parse_port: Parse a comparator and port. + * + * Returns -1 on error, or the comparator. + */ +static int ascend_parse_port(uint16_t *port, char *compare, char *str) +{ + int rcode, token = -1; + + /* + * There MUST be a comparison string. + */ + rcode = fr_str2int(filterCompare, compare, -1); + if (rcode < 0) return rcode; + + if (strspn(str, "0123456789") == strlen(str)) { + token = atoi(str); + } else { + token = fr_str2int(filterPortType, str, -1); + } + + if ((token < 0) || (token > 65535)) return -1; + + *port = token; + *port = htons(*port); + + return rcode; +} + + +#define IP_SRC_ADDR_FLAG (1 << 0) +#define IP_DEST_ADDR_FLAG (1 << 1) +#define IP_SRC_PORT_FLAG (1 << 2) +#define IP_DEST_PORT_FLAG (1 << 3) +#define IP_PROTO_FLAG (1 << 4) +#define IP_EST_FLAG (1 << 5) + +#define DONE_FLAGS (IP_SRC_ADDR_FLAG | IP_DEST_ADDR_FLAG | \ + IP_SRC_PORT_FLAG | IP_DEST_PORT_FLAG | \ + IP_PROTO_FLAG | IP_EST_FLAG) + +/* + * ascend_parse_ip: + * + * This routine parses an IP filter string from a RADIUS + * reply. The format of the string is: + * + * ip dir action [ dstip n.n.n.n/nn ] [ srcip n.n.n.n/nn ] + * [ proto [ dstport cmp value ] [ srcport cmd value ] [ est ] ] + * + * Fields in [...] are optional. + * + * dstip: Keyword for destination IP address. + * n.n.n.n = IP address. /nn - netmask. + * + * srcip: Keyword for source IP address. + * n.n.n.n = IP address. /nn - netmask. + * + * proto: Optional protocol field. Either a name or + * number. Known names are in FilterProtoName[]. + * + * dstport: Keyword for destination port. Only valid with tcp + * or udp. 'cmp' are in FilterPortType[]. 'value' can be + * a name or number. + * + * srcport: Keyword for source port. Only valid with tcp + * or udp. 'cmp' are in FilterPortType[]. 'value' can be + * a name or number. + * + * est: Keyword for TCP established. Valid only for tcp. + * + */ +static int ascend_parse_ip(int argc, char **argv, ascend_ip_filter_t *filter) +{ + int rcode; + int token; + int flags; + + /* + * We may have nothing, in which case we simply return. + */ + if (argc == 0) return 0; + + /* + * There may, or may not, be src & dst IP's in the string. + */ + flags = 0; + while ((argc > 0) && (flags != DONE_FLAGS)) { + token = fr_str2int(filterKeywords, argv[0], -1); + switch (token) { + case FILTER_IP_SRC: + if (flags & IP_SRC_ADDR_FLAG) return -1; + if (argc < 2) return -1; + + rcode = ascend_parse_ipaddr(&filter->srcip, argv[1]); + if (rcode < 0) return rcode; + + filter->srcmask = rcode; + flags |= IP_SRC_ADDR_FLAG; + argv += 2; + argc -= 2; + break; + + case FILTER_IP_DST: + if (flags & IP_DEST_ADDR_FLAG) return -1; + if (argc < 2) return -1; + + rcode = ascend_parse_ipaddr(&filter->dstip, argv[1]); + if (rcode < 0) return rcode; + + filter->dstmask = rcode; + flags |= IP_DEST_ADDR_FLAG; + argv += 2; + argc -= 2; + break; + + case FILTER_IP_SRC_PORT: + if (flags & IP_SRC_PORT_FLAG) return -1; + if (argc < 3) return -1; + + rcode = ascend_parse_port(&filter->srcport, + argv[1], argv[2]); + if (rcode < 0) return rcode; + filter->srcPortComp = rcode; + + flags |= IP_SRC_PORT_FLAG; + argv += 3; + argc -= 3; + break; + + case FILTER_IP_DST_PORT: + if (flags & IP_DEST_PORT_FLAG) return -1; + if (argc < 3) return -1; + + rcode = ascend_parse_port(&filter->dstport, + argv[1], argv[2]); + if (rcode < 0) return rcode; + filter->dstPortComp = rcode; + + flags |= IP_DEST_PORT_FLAG; + argv += 3; + argc -= 3; + break; + + case FILTER_EST: + if (flags & IP_EST_FLAG) return -1; + filter->established = 1; + argv++; + argc--; + flags |= IP_EST_FLAG; + break; + + default: + if (flags & IP_PROTO_FLAG) return -1; + if (strspn(argv[0], "0123456789") == strlen(argv[0])) { + token = atoi(argv[0]); + } else { + token = fr_str2int(filterProtoName, argv[0], -1); + if (token == -1) { + fr_strerror_printf("Unknown IP protocol \"%s\" in IP data filter", + argv[0]); + return -1; + } + } + filter->proto = token; + flags |= IP_PROTO_FLAG; + + argv++; + argc--; + break; + } + } + + /* + * We should have parsed everything by now. + */ + if (argc != 0) { + fr_strerror_printf("Unknown extra string \"%s\" in IP data filter", + argv[0]); + return -1; + } + + return 0; +} + + +/* + * ascend_parse_generic + * + * This routine parses a Generic filter string from a RADIUS + * reply. The format of the string is: + * + * generic dir action offset mask value [== or != ] [more] + * + * Fields in [...] are optional. + * + * offset: A Number. Specifies an offset into a frame + * to start comparing. + * + * mask: A hexadecimal mask of bits to compare. + * + * value: A value to compare with the masked data. + * + * compNeq: Defines type of comparison. ( "==" or "!=") + * Default is "==". + * + * more: Optional keyword MORE, to represent the attachment + * to the next entry. + */ +static int ascend_parse_generic(int argc, char **argv, + ascend_generic_filter_t *filter) +{ + int rcode; + int token; + int flags; + + /* + * We may have nothing, in which case we simply return. + */ + if (argc == 0) return 0; + + /* + * We need at least "offset mask value" + */ + if (argc < 3) return -1; + + /* + * No more than optional comparison and "more" + */ + if (argc > 5) return -1; + + /* + * Offset is a uint16_t number. + */ + if (strspn(argv[0], "0123456789") != strlen(argv[0])) return -1; + + rcode = atoi(argv[0]); + if (rcode > 65535) return -1; + + filter->offset = rcode; + filter->offset = htons(filter->offset); + + rcode = fr_hex2bin(filter->mask, sizeof(filter->mask), argv[1], strlen(argv[1])); + if (rcode != sizeof(filter->mask)) return -1; + + token = fr_hex2bin(filter->value, sizeof(filter->value), argv[2], strlen(argv[2])); + if (token != sizeof(filter->value)) return -1; + + filter->len = rcode; + filter->len = htons(filter->len); + + /* + * Nothing more. Exit. + */ + if (argc == 3) return 0; + + argc -= 3; + argv += 3; + flags = 0; + + while (argc >= 1) { + token = fr_str2int(filterKeywords, argv[0], -1); + switch (token) { + case FILTER_GENERIC_COMPNEQ: + if (flags & 0x01) return -1; + filter->compNeq = true; + flags |= 0x01; + break; + case FILTER_GENERIC_COMPEQ: + if (flags & 0x01) return -1; + filter->compNeq = false; + flags |= 0x01; + break; + + case FILTER_MORE: + if (flags & 0x02) return -1; + filter->more = htons( 1 ); + flags |= 0x02; + break; + + default: + fr_strerror_printf("Invalid string \"%s\" in generic data filter", + argv[0]); + return -1; + } + + argc--; + argv++; + } + + return 0; +} + + +/** Filter binary + * + * This routine will call routines to parse entries from an ASCII format + * to a binary format recognized by the Ascend boxes. + * + * @param out Where to write parsed filter. + * @param value ascend filter text. + * @param len of value. + * @return -1 for error or 0. + */ +int ascend_parse_filter(value_data_t *out, char const *value, size_t len) +{ + int token, type; + int rcode; + int argc; + char *argv[32]; + ascend_filter_t filter; + char *p; + + rcode = -1; + + /* + * Tokenize the input string in the VP. + * + * Once the filter is *completely* parsed, then we will + * over-write it with the final binary filter. + */ + p = talloc_bstrndup(NULL, value, len); + + /* + * Rather than printing specific error messages, we create + * a general one here, which won't be used if the function + * returns OK. + */ + fr_strerror_printf("Failed parsing \"%s\" as ascend filer", p); + + argc = str2argv(p, argv, 32); + if (argc < 3) { + talloc_free(p); + return -1; + } + + /* + * Decide which filter type it is: ip, ipx, or generic + */ + type = fr_str2int(filterType, argv[0], -1); + memset(&filter, 0, sizeof(filter)); + + /* + * Validate the filter type. + */ + switch (type) { + case RAD_FILTER_GENERIC: + case RAD_FILTER_IP: + case RAD_FILTER_IPX: + filter.type = type; + break; + + default: + fr_strerror_printf("Unknown Ascend filter type \"%s\"", argv[0]); + talloc_free(p); + return -1; + } + + /* + * Parse direction + */ + token = fr_str2int(filterKeywords, argv[1], -1); + switch (token) { + case FILTER_IN: + filter.direction = 1; + break; + + case FILTER_OUT: + filter.direction = 0; + break; + + default: + fr_strerror_printf("Unknown Ascend filter direction \"%s\"", argv[1]); + talloc_free(p); + return -1; + } + + /* + * Parse action + */ + token = fr_str2int(filterKeywords, argv[2], -1); + switch (token) { + case FILTER_FORWARD: + filter.forward = 1; + break; + + case FILTER_DROP: + filter.forward = 0; + break; + + default: + fr_strerror_printf("Unknown Ascend filter action \"%s\"", argv[2]); + talloc_free(p); + return -1; + } + + + switch (type) { + case RAD_FILTER_GENERIC: + rcode = ascend_parse_generic(argc - 3, &argv[3], &filter.u.generic); + break; + + case RAD_FILTER_IP: + rcode = ascend_parse_ip(argc - 3, &argv[3], &filter.u.ip); + break; + + case RAD_FILTER_IPX: + rcode = ascend_parse_ipx(argc - 3, &argv[3], &filter.u.ipx); + break; + } + + /* + * Touch the VP only if everything was OK. + */ + if (rcode == 0) memcpy(out->filter, &filter, sizeof(filter)); + talloc_free(p); + + return rcode; +} + +/* + * Print an Ascend binary filter attribute to a string, + * Grrr... Ascend makes the server do this work, instead + * of doing it on the NAS. + * + * Note we don't bother checking 'len' after the snprintf's. + * This function should ONLY be called with a large (~1k) buffer. + */ +void print_abinary(char *out, size_t outlen, uint8_t const *data, size_t len, int8_t quote) +{ + size_t i; + char *p; + ascend_filter_t const *filter; + + static char const *action[] = {"drop", "forward"}; + static char const *direction[] = {"out", "in"}; + + p = out; + + /* + * Just for paranoia: wrong size filters get printed as octets + */ + if (len != sizeof(*filter)) { + strcpy(p, "0x"); + p += 2; + outlen -= 2; + for (i = 0; i < len; i++) { + snprintf(p, outlen, "%02x", data[i]); + p += 2; + outlen -= 2; + } + return; + } + + if (quote > 0) { + *(p++) = (char) quote; + outlen -= 3; /* account for leading & trailing quotes */ + } + + filter = (ascend_filter_t const *) data; + i = snprintf(p, outlen, "%s %s %s", fr_int2str(filterType, filter->type, "??"), + direction[filter->direction & 0x01], action[filter->forward & 0x01]); + + p += i; + outlen -= i; + + /* + * Handle IP filters + */ + if (filter->type == RAD_FILTER_IP) { + + if (filter->u.ip.srcip) { + i = snprintf(p, outlen, " srcip %d.%d.%d.%d/%d", + ((uint8_t const *) &filter->u.ip.srcip)[0], + ((uint8_t const *) &filter->u.ip.srcip)[1], + ((uint8_t const *) &filter->u.ip.srcip)[2], + ((uint8_t const *) &filter->u.ip.srcip)[3], + filter->u.ip.srcmask); + p += i; + outlen -= i; + } + + if (filter->u.ip.dstip) { + i = snprintf(p, outlen, " dstip %d.%d.%d.%d/%d", + ((uint8_t const *) &filter->u.ip.dstip)[0], + ((uint8_t const *) &filter->u.ip.dstip)[1], + ((uint8_t const *) &filter->u.ip.dstip)[2], + ((uint8_t const *) &filter->u.ip.dstip)[3], + filter->u.ip.dstmask); + p += i; + outlen -= i; + } + + i = snprintf(p, outlen, " %s", fr_int2str(filterProtoName, filter->u.ip.proto, "??")); + p += i; + outlen -= i; + + if (filter->u.ip.srcPortComp > RAD_NO_COMPARE) { + i = snprintf(p, outlen, " srcport %s %d", + fr_int2str(filterCompare, filter->u.ip.srcPortComp, "??"), + ntohs(filter->u.ip.srcport)); + p += i; + outlen -= i; + } + + if (filter->u.ip.dstPortComp > RAD_NO_COMPARE) { + i = snprintf(p, outlen, " dstport %s %d", + fr_int2str(filterCompare, filter->u.ip.dstPortComp, "??"), + ntohs(filter->u.ip.dstport)); + p += i; + outlen -= i; + } + + if (filter->u.ip.established) { + i = snprintf(p, outlen, " est"); + p += i; + } + + /* + * Handle IPX filters + */ + } else if (filter->type == RAD_FILTER_IPX) { + /* print for source */ + if (filter->u.ipx.src.net) { + i = snprintf(p, outlen, " srcipxnet 0x%04x srcipxnode 0x%02x%02x%02x%02x%02x%02x", + (unsigned int)ntohl(filter->u.ipx.src.net), + filter->u.ipx.src.node[0], filter->u.ipx.src.node[1], + filter->u.ipx.src.node[2], filter->u.ipx.src.node[3], + filter->u.ipx.src.node[4], filter->u.ipx.src.node[5]); + p += i; + outlen -= i; + + if (filter->u.ipx.srcSocComp > RAD_NO_COMPARE) { + i = snprintf(p, outlen, " srcipxsock %s 0x%04x", + fr_int2str(filterCompare, filter->u.ipx.srcSocComp, "??"), + ntohs(filter->u.ipx.src.socket)); + p += i; + outlen -= i; + } + } + + /* same for destination */ + if (filter->u.ipx.dst.net) { + i = snprintf(p, outlen, " dstipxnet 0x%04x dstipxnode 0x%02x%02x%02x%02x%02x%02x", + (unsigned int)ntohl(filter->u.ipx.dst.net), + filter->u.ipx.dst.node[0], filter->u.ipx.dst.node[1], + filter->u.ipx.dst.node[2], filter->u.ipx.dst.node[3], + filter->u.ipx.dst.node[4], filter->u.ipx.dst.node[5]); + p += i; + outlen -= i; + + if (filter->u.ipx.dstSocComp > RAD_NO_COMPARE) { + i = snprintf(p, outlen, " dstipxsock %s 0x%04x", + fr_int2str(filterCompare, filter->u.ipx.dstSocComp, "??"), + ntohs(filter->u.ipx.dst.socket)); + p += i; + } + } + } else if (filter->type == RAD_FILTER_GENERIC) { + size_t count, masklen; + + masklen = ntohs(filter->u.generic.len); + if (masklen >= sizeof(filter->u.generic.mask)) { + *p = '\0'; + return; + } + + i = snprintf(p, outlen, " %u ", (unsigned int) ntohs(filter->u.generic.offset)); + p += i; + + /* show the mask */ + for (count = 0; count < masklen; count++) { + i = snprintf(p, outlen, "%02x", filter->u.generic.mask[count]); + p += i; + outlen -= i; + } + + strcpy(p, " "); + p++; + outlen--; + + /* show the value */ + for (count = 0; count < masklen; count++) { + i = snprintf(p, outlen, "%02x", filter->u.generic.value[count]); + p += i; + outlen -= i; + } + + i = snprintf(p, outlen, " %s", (filter->u.generic.compNeq) ? "!=" : "=="); + p += i; + outlen -= i; + + if (filter->u.generic.more != 0) { + i = snprintf(p, outlen, " more"); + p += i; + } + } + + if (quote > 0) { + *(p++) = (char) quote; + } + *p = '\0'; +} + +#endif diff --git a/src/lib/getaddrinfo.c b/src/lib/getaddrinfo.c new file mode 100644 index 0000000..1c8aa9a --- /dev/null +++ b/src/lib/getaddrinfo.c @@ -0,0 +1,438 @@ +/* + * These functions are defined and used only if the configure + * cannot detect the standard getaddrinfo(), freeaddrinfo(), + * gai_strerror() and getnameinfo(). This avoids sprinkling of ifdefs. + * + * FIXME: getaddrinfo() & getnameinfo() should + * return all IPv4 addresses provided by DNS lookup. + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#include <ctype.h> +#include <sys/param.h> + +#ifndef HAVE_GETNAMEINFO +# undef LOCAL_GETHOSTBYNAMERSTYLE +# ifndef GETHOSTBYNAMERSTYLE +# define LOCAL_GETHOSTBYNAMERSTYLE 1 +#elif (GETHOSTBYNAMERSTYLE != SYSVSTYLE) && (GETHOSTBYNAMERSTYLE != GNUSTYLE) +# define LOCAL_GETHOSTBYNAMERSTYLE 1 +# endif /* GETHOSTBYNAMERSTYLE */ +#endif + +#ifndef HAVE_GETADDRINFO +# undef LOCAL_GETHOSTBYADDRR +# ifndef GETHOSTBYADDRRSTYLE +# define LOCAL_GETHOSTBYADDRR 1 +# elif (GETHOSTBYADDRRSTYLE != SYSVSTYLE) && (GETHOSTBYADDRRSTYLE != GNUSTYLE) +# define LOCAL_GETHOSTBYADDRR 1 +# endif /* GETHOSTBYADDRRSTYLE */ +#endif + +#ifdef HAVE_PTHREAD_H +# include <pthread.h> + +/* Thread safe DNS lookups */ +/* + * FIXME: There are some systems that use the same hostent + * structure to return for gethostbyname() & gethostbyaddr(), if + * that is the case then use only one mutex instead of separate + * mutexes + */ +# ifdef LOCAL_GETHOSTBYNAMERSTYLE +static int fr_hostbyname = 0; +static pthread_mutex_t fr_hostbyname_mutex; +# endif + +# ifdef LOCAL_GETHOSTBYNAMERSTYLE +static int fr_hostbyaddr = 0; +static pthread_mutex_t fr_hostbyaddr_mutex; +# endif + +#endif + +/* + * gethostbyaddr() & gethostbyname() return hostent structure + * To make these functions thread safe, we need to + * copy the data and not pointers + * + * struct hostent { + * char *h_name; * official name of host * + * char **h_aliases; * alias list * + * int h_addrtype; * host address type * + * int h_length; * length of address * + * char **h_addr_list; * list of addresses * + * } + * This struct contains 3 pointers as members. + * The data from these pointers is copied into a buffer. + * The buffer is formatted as below to store the data + * --------------------------------------------------------------- + * | h_name\0alias_array\0h_aliases\0..\0addr_array\0h_addr_list\0 | + * --------------------------------------------------------------- + */ +#if defined(LOCAL_GETHOSTBYNAMER) || defined(LOCAL_GETHOSTBYADDRR) +# define BUFFER_OVERFLOW 255 +static int copy_hostent(struct hostent *from, struct hostent *to, char *buffer, int buflen, int *error) +{ + int i, len; + char *ptr = buffer; + + *error = 0; + to->h_addrtype = from->h_addrtype; + to->h_length = from->h_length; + to->h_name = (char *)ptr; + + /* copy hostname to buffer */ + len = strlen(from->h_name) + 1; + strcpy(ptr, from->h_name); + ptr += len; + + /* copy aliases to buffer */ + to->h_aliases = (char**)ptr; + for (i = 0; from->h_aliases[i]; i++); + ptr += (i+1) * sizeof(char *); + + for (i = 0; from->h_aliases[i]; i++) { + len = strlen(from->h_aliases[i])+1; + if ((ptr-buffer) + len < buflen) { + to->h_aliases[i] = ptr; + strcpy(ptr, from->h_aliases[i]); + ptr += len; + } else { + *error = BUFFER_OVERFLOW; + return *error; + } + } + to->h_aliases[i] = NULL; + + /* copy addr_list to buffer */ + to->h_addr_list = (char**)ptr; + for (i = 0; (int *)from->h_addr_list[i] != 0; i++); + ptr += (i + 1) * sizeof(int *); + + for (i = 0; (int *)from->h_addr_list[i] != 0; i++) { + len = sizeof(int); + + if ((ptr-buffer)+len < buflen) { + to->h_addr_list[i] = ptr; + memcpy(ptr, from->h_addr_list[i], len); + ptr += len; + } else { + *error = BUFFER_OVERFLOW; + return *error; + } + } + to->h_addr_list[i] = 0; + return *error; +} +#endif /* (LOCAL_GETHOSTBYNAMER == 1) || (LOCAL_GETHOSTBYADDRR == 1) */ + +#ifdef LOCAL_GETHOSTBYNAMERSTYLE +static struct hostent * +gethostbyname_r(char const *hostname, struct hostent *result, + char *buffer, int buflen, int *error) +{ + struct hostent *hp; + +# ifdef HAVE_PTHREAD_H + if (fr_hostbyname == 0) { + pthread_mutex_init(&fr_hostbyname_mutex, NULL); + fr_hostbyname = 1; + } + pthread_mutex_lock(&fr_hostbyname_mutex); +# endif + + hp = gethostbyname(hostname); + if ((!hp) || (hp->h_addrtype != AF_INET) || (hp->h_length != 4)) { + *error = h_errno; + hp = NULL; + } else { + copy_hostent(hp, result, buffer, buflen, error); + hp = result; + } + +# ifdef HAVE_PTHREAD_H + pthread_mutex_unlock(&fr_hostbyname_mutex); +# endif + + return hp; +} +#endif /* GETHOSTBYNAMERSTYLE */ + + +#ifdef LOCAL_GETHOSTBYADDRR +static struct hostent *gethostbyaddr_r(char const *addr, int len, int type, struct hostent *result, + char *buffer, int buflen, int *error) +{ + struct hostent *hp; + +#ifdef HAVE_PTHREAD_H + if (fr_hostbyaddr == 0) { + pthread_mutex_init(&fr_hostbyaddr_mutex, NULL); + fr_hostbyaddr = 1; + } + pthread_mutex_lock(&fr_hostbyaddr_mutex); +#endif + + hp = gethostbyaddr(addr, len, type); + if ((!hp) || (hp->h_addrtype != AF_INET) || (hp->h_length != 4)) { + *error = h_errno; + hp = NULL; + } else { + copy_hostent(hp, result, buffer, buflen, error); + hp = result; + } + +#ifdef HAVE_PTHREAD_H + pthread_mutex_unlock(&fr_hostbyaddr_mutex); +#endif + + return hp; +} +#endif /* GETHOSTBYADDRRSTYLE */ + +/* + * Mar 8, 2000 by Hajimu UMEMOTO <ume@mahoroba.org> + * + * Below code is based on ssh-1.2.27-IPv6-1.5 written by + * KIKUCHI Takahiro <kick@kyoto.wide.ad.jp> + */ + +#ifndef HAVE_GETADDRINFO +static struct addrinfo *malloc_ai(uint16_t port, u_long addr, int socktype, int proto) +{ + struct addrinfo *ai; + + ai = (struct addrinfo *)malloc(sizeof(struct addrinfo) + sizeof(struct sockaddr_in)); + if (!ai) return NULL; + + memset(ai, 0, sizeof(struct addrinfo) + sizeof(struct sockaddr_in)); + ai->ai_addr = (struct sockaddr *)(ai + 1); + ai->ai_addrlen = sizeof(struct sockaddr_in); +# ifdef HAVE_SOCKADDR_SA_LEN + ai->ai_addr->sa_len = sizeof(struct sockaddr_in); +# endif + ai->ai_addr->sa_family = ai->ai_family = AF_INET; + ((struct sockaddr_in *)(ai)->ai_addr)->sin_port = port; + ((struct sockaddr_in *)(ai)->ai_addr)->sin_addr.s_addr = addr; + ai->ai_socktype = socktype; + ai->ai_protocol = proto; + + return ai; +} + +char const *gai_strerror(int ecode) +{ + switch (ecode) { + case EAI_MEMORY: + return "memory allocation failure"; + + case EAI_FAMILY: + return "ai_family not supported"; + + case EAI_NONAME: + return "hostname nor servname provided, or not known"; + + case EAI_SERVICE: + return "servname not supported for ai_socktype"; + + default: + return "unknown error"; + } +} + +void freeaddrinfo(struct addrinfo *ai) +{ + struct addrinfo *next; + + if (ai->ai_canonname) free(ai->ai_canonname); + + do { + next = ai->ai_next; + free(ai); + } while ((ai = next) != NULL); +} + +int getaddrinfo(char const *hostname, char const *servname, struct addrinfo const *hints, struct addrinfo **res) +{ + struct addrinfo *cur, *prev = NULL; + struct hostent *hp; + struct hostent result; + struct in_addr in; + int i, socktype, proto; + uint16_t port = 0; + int error; + char buffer[2048]; + + if (hints && (hints->ai_family != PF_INET) && (hints->ai_family != PF_UNSPEC)) return EAI_FAMILY; + + socktype = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; + if (hints && hints->ai_protocol) { + proto = hints->ai_protocol; + } else { + switch (socktype) { + case SOCK_DGRAM: + proto = IPPROTO_UDP; + break; + case SOCK_STREAM: + proto = IPPROTO_TCP; + break; + default: + proto = 0; + break; + } + } + + if (servname) { + if (isdigit((uint8_t)*servname)) { + port = htons(atoi(servname)); + } else { + struct servent *se; + char const *pe_proto; + + switch (socktype) { + case SOCK_DGRAM: + pe_proto = "udp"; + break; + + case SOCK_STREAM: + pe_proto = "tcp"; + break; + + default: + pe_proto = NULL; + break; + } + if ((se = getservbyname(servname, pe_proto)) == NULL) return EAI_SERVICE; + + port = se->s_port; + } + } + + if (!hostname) { + if (hints && hints->ai_flags & AI_PASSIVE) { + *res = malloc_ai(port, htonl(0x00000000), socktype, proto); + } else { + *res = malloc_ai(port, htonl(0x7f000001), socktype, proto); + } + if (!*res) return EAI_MEMORY; + + return 0; + } + + /* Numeric IP Address */ + if (inet_aton(hostname, &in)) { + *res = malloc_ai(port, in.s_addr, socktype, proto); + if (!*res) return EAI_MEMORY; + + return 0; + } + + if (hints && hints->ai_flags & AI_NUMERICHOST) return EAI_NONAME; + + /* DNS Lookup */ +#ifdef GETHOSTBYNAMERSTYLE +# if GETHOSTBYNAMERSTYLE == SYSVSTYLE + hp = gethostbyname_r(hostname, &result, buffer, sizeof(buffer), &error); +# elif GETHOSTBYNAMERSTYLE == GNUSTYLE + if (gethostbyname_r(hostname, &result, buffer, sizeof(buffer), &hp, &error) != 0) hp = NULL; +# else + hp = gethostbyname_r(hostname, &result, buffer, sizeof(buffer), &error); +# endif +#else + hp = gethostbyname_r(hostname, &result, buffer, sizeof(buffer), &error); +#endif + + if (hp && hp->h_name && hp->h_name[0] && hp->h_addr_list[0]) { + for (i = 0; hp->h_addr_list[i]; i++) { + if ((cur = malloc_ai(port, ((struct in_addr *)hp->h_addr_list[i])->s_addr, + socktype, proto)) == NULL) { + if (*res) freeaddrinfo(*res); + return EAI_MEMORY; + } + + if (prev) { + prev->ai_next = cur; + } else { + *res = cur; + } + prev = cur; + } + + if (hints && hints->ai_flags & AI_CANONNAME && *res) { + if (((*res)->ai_canonname = strdup(hp->h_name)) == NULL) { + freeaddrinfo(*res); + return EAI_MEMORY; + } + } + return 0; + } + return EAI_NONAME; +} +#endif /* HAVE_GETADDRINFO */ + + +#ifndef HAVE_GETNAMEINFO +int getnameinfo(struct sockaddr const *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, + unsigned int flags) +{ + const struct sockaddr_in *sin = (struct sockaddr_in const *)sa; + struct hostent *hp; + struct hostent result; + char tmpserv[16]; + char buffer[2048]; + int error; + + if (serv) { + snprintf(tmpserv, sizeof(tmpserv), "%d", ntohs(sin->sin_port)); + if (strlen(tmpserv) > servlen) return EAI_MEMORY; + + strcpy(serv, tmpserv); + + if (host) { + if (flags & NI_NUMERICHOST) { + /* No Reverse DNS lookup */ + if (flags & NI_NAMEREQD) return EAI_NONAME; + if (strlen(inet_ntoa(sin->sin_addr)) >= hostlen) return EAI_MEMORY; + + strcpy(host, inet_ntoa(sin->sin_addr)); + return 0; + } else { + /* Reverse DNS lookup required */ +#ifdef GETHOSTBYADDRRSTYLE +# if GETHOSTBYADDRRSTYLE == SYSVSTYLE + hp = gethostbyaddr_r((char const *)&sin->sin_addr, + salen, AF_INET, &result, buffer, sizeof(buffer), &error); +# elif GETHOSTBYADDRRSTYLE == GNUSTYLE + if (gethostbyaddr_r((char const *)&sin->sin_addr, salen, AF_INET, + &result, buffer, sizeof(buffer), &hp, &error) != 0) { + hp = NULL; + } +# else + hp = gethostbyaddr_r((char const *)&sin->sin_addr, salen, AF_INET, + &result, buffer, sizeof(buffer), &error); +# endif +#else + hp = gethostbyaddr_r((char const *)&sin->sin_addr, salen, AF_INET, + &result, buffer, sizeof(buffer), &error); +#endif + if (hp) { + if (strlen(hp->h_name) >= hostlen) return EAI_MEMORY; + + strcpy(host, hp->h_name); + return 0; + } + + if (flags & NI_NAMEREQD) return EAI_NONAME; + if (strlen(inet_ntoa(sin->sin_addr)) >= hostlen) return EAI_MEMORY; + + strcpy(host, inet_ntoa(sin->sin_addr)); + return 0; + } + } + return 0; +} +#endif /* HAVE_GETNAMEINFO */ diff --git a/src/lib/hash.c b/src/lib/hash.c new file mode 100644 index 0000000..9efff6e --- /dev/null +++ b/src/lib/hash.c @@ -0,0 +1,935 @@ +/* + * hash.c Non-thread-safe split-ordered hash table. + * + * The weird "reverse" function is based on an idea from + * "Split-Ordered Lists - Lock-free Resizable Hash Tables", with + * modifications so that they're not lock-free. :( + * + * However, the split-order idea allows a fast & easy splitting of the + * hash bucket chain when the hash table is resized. Without it, we'd + * have to check & update the pointers for every node in the buck chain, + * rather than being able to move 1/2 of the entries in the chain with + * one update. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2005,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +/* + * A reasonable number of buckets to start off with. + * Should be a power of two. + */ +#define FR_HASH_NUM_BUCKETS (64) + +struct fr_hash_entry_s { + fr_hash_entry_t *next; + uint32_t reversed; + uint32_t key; + void const *data; +}; + + +struct fr_hash_table_t { + int num_elements; + int num_buckets; /* power of 2 */ + int next_grow; + int mask; + + fr_hash_table_free_t free; + fr_hash_table_hash_t hash; + fr_hash_table_cmp_t cmp; + + fr_hash_entry_t null; + + fr_hash_entry_t **buckets; +}; + +#ifdef TESTING +static int grow = 0; +#endif + +/* + * perl -e 'foreach $i (0..255) {$r = 0; foreach $j (0 .. 7 ) { if (($i & ( 1<< $j)) != 0) { $r |= (1 << (7 - $j));}} print $r, ", ";if (($i & 7) == 7) {print "\n";}}' + */ +static const uint8_t reversed_byte[256] = { + 0, 128, 64, 192, 32, 160, 96, 224, + 16, 144, 80, 208, 48, 176, 112, 240, + 8, 136, 72, 200, 40, 168, 104, 232, + 24, 152, 88, 216, 56, 184, 120, 248, + 4, 132, 68, 196, 36, 164, 100, 228, + 20, 148, 84, 212, 52, 180, 116, 244, + 12, 140, 76, 204, 44, 172, 108, 236, + 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, + 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, + 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, + 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, + 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, + 17, 145, 81, 209, 49, 177, 113, 241, + 9, 137, 73, 201, 41, 169, 105, 233, + 25, 153, 89, 217, 57, 185, 121, 249, + 5, 133, 69, 197, 37, 165, 101, 229, + 21, 149, 85, 213, 53, 181, 117, 245, + 13, 141, 77, 205, 45, 173, 109, 237, + 29, 157, 93, 221, 61, 189, 125, 253, + 3, 131, 67, 195, 35, 163, 99, 227, + 19, 147, 83, 211, 51, 179, 115, 243, + 11, 139, 75, 203, 43, 171, 107, 235, + 27, 155, 91, 219, 59, 187, 123, 251, + 7, 135, 71, 199, 39, 167, 103, 231, + 23, 151, 87, 215, 55, 183, 119, 247, + 15, 143, 79, 207, 47, 175, 111, 239, + 31, 159, 95, 223, 63, 191, 127, 255 +}; + + +/* + * perl -e 'foreach $i (0..255) {$r = 0;foreach $j (0 .. 7) { $r = $i & (1 << (7 - $j)); last if ($r)} print $i & ~($r), ", ";if (($i & 7) == 7) {print "\n";}}' + */ +static uint8_t parent_byte[256] = { + 0, 0, 0, 1, 0, 1, 2, 3, + 0, 1, 2, 3, 4, 5, 6, 7, + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 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, 26, 27, 28, 29, 30, 31, + 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, 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, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 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, 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, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127 +}; + + +/* + * Reverse a key. + */ +static uint32_t reverse(uint32_t key) +{ + /* + * Cast to uint32_t is required because the + * default type of of the expression is an + * int and ubsan correctly complains that + * the result of 0xff << 24 won't fit in an + * unsigned 32bit integer. + */ + return (((uint32_t)reversed_byte[key & 0xff] << 24) | + ((uint32_t)reversed_byte[(key >> 8) & 0xff] << 16) | + ((uint32_t)reversed_byte[(key >> 16) & 0xff] << 8) | + ((uint32_t)reversed_byte[(key >> 24) & 0xff])); +} + +/* + * Take the parent by discarding the highest bit that is set. + */ +static uint32_t parent_of(uint32_t key) +{ + if (key > 0x00ffffff) + return (key & 0x00ffffff) | (parent_byte[key >> 24] << 24); + + if (key > 0x0000ffff) + return (key & 0x0000ffff) | (parent_byte[key >> 16] << 16); + + if (key > 0x000000ff) + return (key & 0x000000ff) | (parent_byte[key >> 8] << 8); + + return parent_byte[key]; +} + + +static fr_hash_entry_t *list_find(fr_hash_table_t *ht, + fr_hash_entry_t *head, + uint32_t reversed, + void const *data) +{ + fr_hash_entry_t *cur; + + for (cur = head; cur != &ht->null; cur = cur->next) { + if (cur->reversed == reversed) { + if (ht->cmp) { + int cmp = ht->cmp(data, cur->data); + if (cmp > 0) break; + if (cmp < 0) continue; + } + return cur; + } + if (cur->reversed > reversed) break; + } + + return NULL; +} + + +/* + * Inserts a new entry into the list, in order. + */ +static int list_insert(fr_hash_table_t *ht, + fr_hash_entry_t **head, fr_hash_entry_t *node) +{ + fr_hash_entry_t **last, *cur; + + last = head; + + for (cur = *head; cur != &ht->null; cur = cur->next) { + if (cur->reversed > node->reversed) break; + last = &(cur->next); + + if (cur->reversed == node->reversed) { + if (ht->cmp) { + int cmp = ht->cmp(node->data, cur->data); + if (cmp > 0) break; + if (cmp < 0) continue; + } + return 0; + } + } + + node->next = *last; + *last = node; + + return 1; +} + + +/* + * Delete an entry from the list. + */ +static int list_delete(fr_hash_table_t *ht, + fr_hash_entry_t **head, fr_hash_entry_t *node) +{ + fr_hash_entry_t **last, *cur; + + last = head; + + for (cur = *head; cur != &ht->null; cur = cur->next) { + if (cur == node) break; + last = &(cur->next); + } + + *last = node->next; + return 1; +} + + +/* + * Create the table. + * + * Memory usage in bytes is (20/3) * number of entries. + */ +fr_hash_table_t *fr_hash_table_create(fr_hash_table_hash_t hashNode, + fr_hash_table_cmp_t cmpNode, + fr_hash_table_free_t freeNode) +{ + fr_hash_table_t *ht; + + if (!hashNode) return NULL; + + ht = malloc(sizeof(*ht)); + if (!ht) return NULL; + + memset(ht, 0, sizeof(*ht)); + ht->free = freeNode; + ht->hash = hashNode; + ht->cmp = cmpNode; + ht->num_buckets = FR_HASH_NUM_BUCKETS; + ht->mask = ht->num_buckets - 1; + + /* + * Have a default load factor of 2.5. In practice this + * means that the average load will hit 3 before the + * table grows. + */ + ht->next_grow = (ht->num_buckets << 1) + (ht->num_buckets >> 1); + + ht->buckets = malloc(sizeof(*ht->buckets) * ht->num_buckets); + if (!ht->buckets) { + free(ht); + return NULL; + } + memset(ht->buckets, 0, sizeof(*ht->buckets) * ht->num_buckets); + + ht->null.reversed = ~0; + ht->null.key = ~0; + ht->null.next = &ht->null; + + ht->buckets[0] = &ht->null; + + return ht; +} + + +/* + * If the current bucket is uninitialized, initialize it + * by recursively copying information from the parent. + * + * We may have a situation where entry E is a parent to 2 other + * entries E' and E". If we split E into E and E', then the + * nodes meant for E" end up in E or E', either of which is + * wrong. To solve that problem, we walk down the whole chain, + * inserting the elements into the correct place. + */ +static void fr_hash_table_fixup(fr_hash_table_t *ht, uint32_t entry) +{ + uint32_t parent_entry; + fr_hash_entry_t **last, *cur; + uint32_t this; + + parent_entry = parent_of(entry); + + /* parent_entry == entry if and only if entry == 0 */ + + if (!ht->buckets[parent_entry]) { + fr_hash_table_fixup(ht, parent_entry); + } + + /* + * Keep walking down cur, trying to find entries that + * don't belong here any more. There may be multiple + * ones, so we can't have a naive algorithm... + */ + last = &ht->buckets[parent_entry]; + this = parent_entry; + + for (cur = *last; cur != &ht->null; cur = cur->next) { + uint32_t real_entry; + + real_entry = cur->key & ht->mask; + if (real_entry != this) { /* ht->buckets[real_entry] == NULL */ + *last = &ht->null; + ht->buckets[real_entry] = cur; + this = real_entry; + } + + last = &(cur->next); + } + + /* + * We may NOT have initialized this bucket, so do it now. + */ + if (!ht->buckets[entry]) ht->buckets[entry] = &ht->null; +} + +/* + * This should be a power of two. Changing it to 4 doesn't seem + * to make any difference. + */ +#define GROW_FACTOR (2) + +/* + * Grow the hash table. + */ +static void fr_hash_table_grow(fr_hash_table_t *ht) +{ + fr_hash_entry_t **buckets; + + buckets = malloc(sizeof(*buckets) * GROW_FACTOR * ht->num_buckets); + if (!buckets) return; + + memcpy(buckets, ht->buckets, + sizeof(*buckets) * ht->num_buckets); + memset(&buckets[ht->num_buckets], 0, + sizeof(*buckets) * ht->num_buckets); + + free(ht->buckets); + ht->buckets = buckets; + ht->num_buckets *= GROW_FACTOR; + ht->next_grow *= GROW_FACTOR; + ht->mask = ht->num_buckets - 1; +#ifdef TESTING + grow = 1; + fprintf(stderr, "GROW TO %d\n", ht->num_buckets); +#endif +} + + +/* + * Insert data. + */ +int fr_hash_table_insert(fr_hash_table_t *ht, void const *data) +{ + uint32_t key; + uint32_t entry; + uint32_t reversed; + fr_hash_entry_t *node; + + if (!ht || !data) return 0; + + key = ht->hash(data); + entry = key & ht->mask; + reversed = reverse(key); + + if (!ht->buckets[entry]) fr_hash_table_fixup(ht, entry); + + /* + * If we try to do our own memory allocation here, the + * speedup is only ~15% or so, which isn't worth it. + */ + node = malloc(sizeof(*node)); + if (!node) return 0; + memset(node, 0, sizeof(*node)); + + node->next = &ht->null; + node->reversed = reversed; + node->key = key; + node->data = data; + + /* already in the table, can't insert it */ + if (!list_insert(ht, &ht->buckets[entry], node)) { + free(node); + return 0; + } + + /* + * Check the load factor, and grow the table if + * necessary. + */ + ht->num_elements++; + if (ht->num_elements >= ht->next_grow) { + fr_hash_table_grow(ht); + } + + return 1; +} + + +/* + * Internal find a node routine. + */ +static fr_hash_entry_t *fr_hash_table_find(fr_hash_table_t *ht, void const *data) +{ + uint32_t key; + uint32_t entry; + uint32_t reversed; + + if (!ht) return NULL; + + key = ht->hash(data); + entry = key & ht->mask; + reversed = reverse(key); + + if (!ht->buckets[entry]) fr_hash_table_fixup(ht, entry); + + return list_find(ht, ht->buckets[entry], reversed, data); +} + + +/* + * Replace old data with new data, OR insert if there is no old. + */ +int fr_hash_table_replace(fr_hash_table_t *ht, void const *data) +{ + fr_hash_entry_t *node; + void *tofree; + + if (!ht || !data) return 0; + + node = fr_hash_table_find(ht, data); + if (!node) { + return fr_hash_table_insert(ht, data); + } + + if (ht->free) { + memcpy(&tofree, &node->data, sizeof(tofree)); + ht->free(tofree); + } + node->data = data; + + return 1; +} + + +/* + * Find data from a template + */ +void *fr_hash_table_finddata(fr_hash_table_t *ht, void const *data) +{ + fr_hash_entry_t *node; + void *out; + + node = fr_hash_table_find(ht, data); + if (!node) return NULL; + + memcpy(&out, &node->data, sizeof(out)); + + return out; +} + + + +/* + * Yank an entry from the hash table, without freeing the data. + */ +void *fr_hash_table_yank(fr_hash_table_t *ht, void const *data) +{ + uint32_t key; + uint32_t entry; + uint32_t reversed; + void *old; + fr_hash_entry_t *node; + + if (!ht) return NULL; + + key = ht->hash(data); + entry = key & ht->mask; + reversed = reverse(key); + + if (!ht->buckets[entry]) fr_hash_table_fixup(ht, entry); + + node = list_find(ht, ht->buckets[entry], reversed, data); + if (!node) return NULL; + + list_delete(ht, &ht->buckets[entry], node); + ht->num_elements--; + + memcpy(&old, &node->data, sizeof(old)); + free(node); + + return old; +} + + +/* + * Delete a piece of data from the hash table. + */ +int fr_hash_table_delete(fr_hash_table_t *ht, void const *data) +{ + void *old; + + old = fr_hash_table_yank(ht, data); + if (!old) return 0; + + if (ht->free) ht->free(old); + + return 1; +} + + +/* + * Free a hash table + */ +void fr_hash_table_free(fr_hash_table_t *ht) +{ + int i; + fr_hash_entry_t *node, *next; + + if (!ht) return; + + /* + * Walk over the buckets, freeing them all. + */ + for (i = 0; i < ht->num_buckets; i++) { + if (ht->buckets[i]) for (node = ht->buckets[i]; + node != &ht->null; + node = next) { + next = node->next; + + if (node->data && ht->free) { + void *tofree; + memcpy(&tofree, &node->data, sizeof(tofree)); + ht->free(tofree); + } + + free(node); + } + } + + free(ht->buckets); + free(ht); +} + + +/* + * Count number of elements + */ +int fr_hash_table_num_elements(fr_hash_table_t *ht) +{ + if (!ht) return 0; + + return ht->num_elements; +} + + +/* + * Walk over the nodes, allowing deletes & inserts to happen. + */ +int fr_hash_table_walk(fr_hash_table_t *ht, + fr_hash_table_walk_t callback, + void *context) +{ + int i, rcode; + + if (!ht || !callback) return 0; + + for (i = ht->num_buckets - 1; i >= 0; i--) { + fr_hash_entry_t *node, *next; + + /* + * Ensure that the current bucket is filled. + */ + if (!ht->buckets[i]) fr_hash_table_fixup(ht, i); + + for (node = ht->buckets[i]; node != &ht->null; node = next) { + void *arg; + + next = node->next; + + memcpy(&arg, &node->data, sizeof(arg)); + rcode = callback(context, arg); + + if (rcode != 0) return rcode; + } + } + + return 0; +} + +/** Iterate over entries in a hash table + * + * @note If the hash table is modified the iterator should be considered invalidated. + * + * @param[in] ht to iterate over. + * @param[in] iter Pointer to an iterator struct, used to maintain + * state between calls. + * @return + * - User data. + * - NULL if at the end of the list. + */ +void *fr_hash_table_iter_next(fr_hash_table_t *ht, fr_hash_iter_t *iter) +{ + fr_hash_entry_t *node; + uint32_t i; + void *out; + + /* + * Return the next element in the bucket + */ + if (iter->node != &ht->null) { + node = iter->node; + iter->node = node->next; + + memcpy(&out, &node->data, sizeof(out)); /* const issues */ + return out; + } + + if (iter->bucket == 0) return NULL; + + /* + * We might have to go through multiple empty + * buckets to find one that contains something + * we should return + */ + i = iter->bucket - 1; + for (;;) { + if (!ht->buckets[i]) fr_hash_table_fixup(ht, i); + + node = ht->buckets[i]; + if (node == &ht->null) { + if (i == 0) break; + i--; + continue; /* This bucket was empty too... */ + } + + iter->node = node->next; /* Store the next one to examine */ + iter->bucket = i; + + memcpy(&out, &node->data, sizeof(out)); /* const issues */ + return out; + } + iter->bucket = i; + + return NULL; +} + +/** Initialise an iterator + * + * @note If the hash table is modified the iterator should be considered invalidated. + * + * @param[in] ht to iterate over. + * @param[out] iter to initialise. + * @return + * - The first entry in the hash table. + * - NULL if the hash table is empty. + */ +void *fr_hash_table_iter_init(fr_hash_table_t *ht, fr_hash_iter_t *iter) +{ + iter->bucket = ht->num_buckets; + iter->node = &ht->null; + + return fr_hash_table_iter_next(ht, iter); +} + + +#ifdef TESTING +/* + * Show what the hash table is doing. + */ +int fr_hash_table_info(fr_hash_table_t *ht) +{ + int i, a, collisions, uninitialized; + int array[256]; + + if (!ht) return 0; + + uninitialized = collisions = 0; + memset(array, 0, sizeof(array)); + + for (i = 0; i < ht->num_buckets; i++) { + uint32_t key; + int load; + fr_hash_entry_t *node, *next; + + /* + * If we haven't inserted or looked up an entry + * in a bucket, it's uninitialized. + */ + if (!ht->buckets[i]) { + uninitialized++; + continue; + } + + load = 0; + key = ~0; + for (node = ht->buckets[i]; node != &ht->null; node = next) { + if (node->reversed == key) { + collisions++; + } else { + key = node->reversed; + } + next = node->next; + load++; + } + + if (load > 255) load = 255; + array[load]++; + } + + printf("HASH TABLE %p\tbuckets: %d\t(%d uninitialized)\n", ht, + ht->num_buckets, uninitialized); + printf("\tnum entries %d\thash collisions %d\n", + ht->num_elements, collisions); + + a = 0; + for (i = 1; i < 256; i++) { + if (!array[i]) continue; + printf("%d\t%d\n", i, array[i]); + + /* + * Since the entries are ordered, the lookup cost + * for any one element in a chain is (on average) + * the cost of walking half of the chain. + */ + if (i > 1) { + a += array[i] * i; + } + } + a /= 2; + a += array[1]; + + printf("\texpected lookup cost = %d/%d or %f\n\n", + ht->num_elements, a, + (float) ht->num_elements / (float) a); + + return 0; +} +#endif + + +#define FNV_MAGIC_INIT (0x811c9dc5) +#define FNV_MAGIC_PRIME (0x01000193) + +/* + * A fast hash function. For details, see: + * + * http://www.isthe.com/chongo/tech/comp/fnv/ + * + * Which also includes public domain source. We've re-written + * it here for our purposes. + */ +uint32_t fr_hash(void const *data, size_t size) +{ + uint8_t const *p = data; + uint8_t const *q = p + size; + uint32_t hash = FNV_MAGIC_INIT; + + /* + * FNV-1 hash each octet in the buffer + */ + while (p != q) { + /* + * XOR the 8-bit quantity into the bottom of + * the hash. + */ + hash ^= (uint32_t) (*p++); + + /* + * Multiple by 32-bit magic FNV prime, mod 2^32 + */ + hash *= FNV_MAGIC_PRIME; +#if 0 + /* + * Potential optimization. + */ + hash += (hash<<1) + (hash<<4) + (hash<<7) + (hash<<8) + (hash<<24); +#endif + } + + return hash; +} + +/* + * Continue hashing data. + */ +uint32_t fr_hash_update(void const *data, size_t size, uint32_t hash) +{ + uint8_t const *p = data; + uint8_t const *q = p + size; + + while (p != q) { + hash *= FNV_MAGIC_PRIME; + hash ^= (uint32_t) (*p++); + } + + return hash; + +} + +/* + * Hash a C string, so we loop over it once. + */ +uint32_t fr_hash_string(char const *p) +{ + uint32_t hash = FNV_MAGIC_INIT; + + while (*p) { + hash *= FNV_MAGIC_PRIME; + hash ^= (uint32_t) (*p++); + } + + return hash; +} + + +#ifdef TESTING +/* + * cc -g -DTESTING -I ../include hash.c -o hash + * + * ./hash + */ +static uint32_t hash_int(void const *data) +{ + return fr_hash((int *) data, sizeof(int)); +} + +#define MAX 1024*1024 +int main(int argc, char **argv) +{ + int i, *p, *q, k; + fr_hash_table_t *ht; + int *array; + + ht = fr_hash_table_create(hash_int, NULL, NULL); + if (!ht) { + fprintf(stderr, "Hash create failed\n"); + fr_exit(1); + } + + array = malloc(sizeof(int) * MAX); + if (!array) fr_exit(1); + + for (i = 0; i < MAX; i++) { + p = array + i; + *p = i; + + if (!fr_hash_table_insert(ht, p)) { + fprintf(stderr, "Failed insert %08x\n", i); + fr_exit(1); + } +#ifdef TEST_INSERT + q = fr_hash_table_finddata(ht, p); + if (q != p) { + fprintf(stderr, "Bad data %d\n", i); + fr_exit(1); + } +#endif + } + + fr_hash_table_info(ht); + + /* + * Build this to see how lookups result in shortening + * of the hash chains. + */ + if (1) { + for (i = 0; i < MAX ; i++) { + q = fr_hash_table_finddata(ht, &i); + if (!q || *q != i) { + fprintf(stderr, "Failed finding %d\n", i); + fr_exit(1); + } + +#if 0 + if (!fr_hash_table_delete(ht, &i)) { + fprintf(stderr, "Failed deleting %d\n", i); + fr_exit(1); + } + q = fr_hash_table_finddata(ht, &i); + if (q) { + fprintf(stderr, "Failed to delete %08x\n", i); + fr_exit(1); + } +#endif + } + + fr_hash_table_info(ht); + } + + fr_hash_table_free(ht); + free(array); + + fr_exit(0); +} +#endif diff --git a/src/lib/heap.c b/src/lib/heap.c new file mode 100644 index 0000000..a2e8f78 --- /dev/null +++ b/src/lib/heap.c @@ -0,0 +1,356 @@ +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/heap.h> + +/* + * A heap entry is made of a pointer to the object, which + * contains the key. The heap itself is an array of pointers. + * + * Heaps normally support only ordered insert, and extraction + * of the minimum element. The heap entry can contain an "int" + * field that holds the entries position in the heap. The offset + * of the field is held inside of the heap structure. + */ + +struct fr_heap_t { + int size; + int num_elements; + size_t offset; + fr_heap_cmp_t cmp; + void **p; +}; + +/* + * First node in a heap is element 0. Children of i are 2i+1 and + * 2i+2. These macros wrap the logic, so the code is more + * descriptive. + */ +#define HEAP_PARENT(x) ( ( (x) - 1 ) / 2 ) +#define HEAP_LEFT(x) ( 2*(x) + 1 ) +/* #define HEAP_RIGHT(x) ( 2*(x) + 2 ) */ +#define HEAP_SWAP(a, b) { void *_tmp = a; a = b; b = _tmp; } + +static int fr_heap_bubble(fr_heap_t *hp, int child); + +void fr_heap_delete(fr_heap_t *hp) +{ + if (!hp) return; + + free(hp->p); + free(hp); +} + +fr_heap_t *fr_heap_create(fr_heap_cmp_t cmp, size_t offset) +{ + fr_heap_t *fh; + + if (!cmp) return NULL; + + fh = malloc(sizeof(*fh)); + if (!fh) return NULL; + + memset(fh, 0, sizeof(*fh)); + + fh->size = 2048; + fh->p = malloc(sizeof(*(fh->p)) * fh->size); + if (!fh->p) { + free(fh); + return NULL; + } + + fh->cmp = cmp; + fh->offset = offset; + + return fh; +} + +/* + * Insert element in heap. Normally, p != NULL, we insert p in a + * new position and bubble up. If p == NULL, then the element is + * already in place, and key is the position where to start the + * bubble-up. + * + * Returns 1 on failure (cannot allocate new heap entry) + * + * If offset > 0 the position (index, int) of the element in the + * heap is also stored in the element itself at the given offset + * in bytes. + */ +#define SET_OFFSET(heap, node) \ + if (heap->offset) \ + *((int *)(((uint8_t *)heap->p[node]) + heap->offset)) = node + +/* + * RESET_OFFSET is used for sanity checks. It sets offset to an + * invalid value. + */ +#define RESET_OFFSET(heap, node) \ + if (heap->offset) \ + *((int *)(((uint8_t *)heap->p[node]) + heap->offset)) = -1 + +int fr_heap_insert(fr_heap_t *hp, void *data) +{ + int child = hp->num_elements; + + /* + * Heap is full. Double it's size. + */ + if (child == hp->size) { + void **p; + + p = malloc(2 * hp->size * sizeof(*p)); + if (!p) return 0; + + memcpy(p, hp->p, sizeof(*p) * hp->size); + free(hp->p); + hp->p = p; + hp->size *= 2; + } + + hp->p[child] = data; + hp->num_elements++; + + return fr_heap_bubble(hp, child); +} + + +static int fr_heap_bubble(fr_heap_t *hp, int child) +{ + /* + * Bubble up the element. + */ + while (child > 0) { + int parent = HEAP_PARENT(child); + + /* + * Parent is smaller than the child. We're done. + */ + if (hp->cmp(hp->p[parent], hp->p[child]) < 0) break; + + /* + * Child is smaller than the parent, repeat. + */ + HEAP_SWAP(hp->p[child], hp->p[parent]); + SET_OFFSET(hp, child); + child = parent; + } + SET_OFFSET(hp, child); + + return 1; +} + + +/* + * Remove the top element, or object. + */ +int fr_heap_extract(fr_heap_t *hp, void *data) +{ + int child, parent; + int max; + + if (!hp || (hp->num_elements == 0)) return 0; + + max = hp->num_elements - 1; + + /* + * Extract element. Default is the first one. + */ + if (!data) { + parent = 0; + + } else { /* extract from the middle */ + if (!hp->offset) return 0; + + parent = *((int *)(((uint8_t *)data) + hp->offset)); + + /* + * Out of bounds. + */ + if (parent < 0 || parent >= hp->num_elements) return 0; + } + + RESET_OFFSET(hp, parent); + child = HEAP_LEFT(parent); + while (child <= max) { + /* + * Maybe take the right child. + */ + if ((child != max) && + (hp->cmp(hp->p[child + 1], hp->p[child]) < 0)) { + child = child + 1; + } + hp->p[parent] = hp->p[child]; + SET_OFFSET(hp, parent); + parent = child; + child = HEAP_LEFT(child); + } + hp->num_elements--; + + /* + * We didn't end up at the last element in the heap. + * This element has to be re-inserted. + */ + if (parent != max) { + /* + * Fill hole with last entry and bubble up, + * reusing the insert code + */ + hp->p[parent] = hp->p[max]; + return fr_heap_bubble(hp, parent); + } + + return 1; +} + + +void *fr_heap_peek(fr_heap_t *hp) +{ + if (!hp || (hp->num_elements == 0)) return NULL; + + /* + * If this is NULL, we have a problem. + */ + return hp->p[0]; +} + +int fr_heap_num_elements(fr_heap_t *hp) +{ + if (!hp) return 0; + + return hp->num_elements; +} + + +#ifdef TESTING +static bool fr_heap_check(fr_heap_t *hp, void *data) +{ + int i; + + if (!hp || (hp->num_elements == 0)) return false; + + for (i = 0; i < hp->num_elements; i++) { + if (hp->p[i] == data) { + return true; + } + } + + return false; +} + +typedef struct heap_thing { + int data; + int heap; /* for the heap */ +} heap_thing; + + +/* + * cc -g -DTESTING -I .. heap.c -o heap + * + * ./heap + */ +static int heap_cmp(void const *one, void const *two) +{ + heap_thing const *a; + heap_thing const *b; + + a = (heap_thing const *) one; + b = (heap_thing const *) two; + + return a->data - b->data; + +} + +#define ARRAY_SIZE (1024) + +int main(int argc, char **argv) +{ + fr_heap_t *hp; + int i; + heap_thing array[ARRAY_SIZE]; + int skip = 0; + int left; + + if (argc > 1) { + skip = atoi(argv[1]); + } + + hp = fr_heap_create(heap_cmp, offsetof(heap_thing, heap)); + if (!hp) { + fprintf(stderr, "Failed creating heap!\n"); + fr_exit(1); + } + + for (i = 0; i < ARRAY_SIZE; i++) { + array[i].data = rand() % 65537; + if (!fr_heap_insert(hp, &array[i])) { + fprintf(stderr, "Failed inserting %d\n", i); + fr_exit(1); + } + + if (!fr_heap_check(hp, &array[i])) { + fprintf(stderr, "Inserted but not in heap %d\n", i); + fr_exit(1); + } + } + +#if 0 + for (i = 0; i < ARRAY_SIZE; i++) { + printf("Array %d has value %d at offset %d\n", + i, array[i].data, array[i].heap); + } +#endif + + if (skip) { + int entry; + + printf("%d elements to remove\n", ARRAY_SIZE / skip); + + for (i = 0; i < ARRAY_SIZE / skip; i++) { + entry = i * skip; + + if (!fr_heap_extract(hp, &array[entry])) { + fprintf(stderr, "Failed removing %d\n", entry); + } + + if (fr_heap_check(hp, &array[entry])) { + fprintf(stderr, "Deleted but still in heap %d\n", entry); + fr_exit(1); + } + + if (array[entry].heap != -1) { + fprintf(stderr, "heap offset is wrong %d\n", entry); + fr_exit(1); + } + } + } + + left = fr_heap_num_elements(hp); + printf("%d elements left in the heap\n", left); + + for (i = 0; i < left; i++) { + heap_thing *t = fr_heap_peek(hp); + + if (!t) { + fprintf(stderr, "Failed peeking %d\n", i); + fr_exit(1); + } + + printf("%d\t%d\n", i, t->data); + + if (!fr_heap_extract(hp, NULL)) { + fprintf(stderr, "Failed extracting %d\n", i); + fr_exit(1); + } + } + + if (fr_heap_num_elements(hp) > 0) { + fprintf(stderr, "%d elements left at the end", fr_heap_num_elements(hp)); + fr_exit(1); + } + + fr_heap_delete(hp); + + return 0; +} +#endif diff --git a/src/lib/hmacmd5.c b/src/lib/hmacmd5.c new file mode 100644 index 0000000..2aad490 --- /dev/null +++ b/src/lib/hmacmd5.c @@ -0,0 +1,197 @@ +/* + * hmac.c For the sake of illustration we provide the following + * sample code for the implementation of HMAC-MD5 as well + * as some corresponding test vectors (the code is based + * on MD5 code as described in [MD5]). + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +/* +** Function: fr_hmac_md5 +*/ + +RCSID("$Id$") + +#ifdef HAVE_OPENSSL_EVP_H +#include <openssl/hmac.h> +#include <openssl/evp.h> +#endif + +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/md5.h> +#include <freeradius-devel/openssl3.h> + +#if defined(HAVE_OPENSSL_EVP_H) && !defined(WITH_FIPS) +/** Calculate HMAC using OpenSSL's MD5 implementation + * + * @param digest Caller digest to be filled in. + * @param text Pointer to data stream. + * @param text_len length of data stream. + * @param key Pointer to authentication key. + * @param key_len Length of authentication key. + * + */ +void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t text_len, + uint8_t const *key, size_t key_len) +{ + HMAC_CTX *ctx = HMAC_CTX_new(); + unsigned int len = MD5_DIGEST_LENGTH; + +#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW + /* Since MD5 is not allowed by FIPS, explicitly allow it. */ + HMAC_CTX_set_flags(ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); +#endif /* EVP_MD_CTX_FLAG_NON_FIPS_ALLOW */ + + HMAC_Init_ex(ctx, key, key_len, EVP_md5(), NULL); + HMAC_Update(ctx, text, text_len); + HMAC_Final(ctx, digest, &len); + HMAC_CTX_free(ctx); +} +#else +/** Calculate HMAC using internal MD5 implementation + * + * @param digest Caller digest to be filled in. + * @param text Pointer to data stream. + * @param text_len length of data stream. + * @param key Pointer to authentication key. + * @param key_len Length of authentication key. + * + */ +void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t text_len, + uint8_t const *key, size_t key_len) +{ + FR_MD5_CTX context; + uint8_t k_ipad[65]; /* inner padding - key XORd with ipad */ + uint8_t k_opad[65]; /* outer padding - key XORd with opad */ + uint8_t tk[16]; + int i; + + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) { + FR_MD5_CTX tctx; + + fr_md5_init(&tctx); + fr_md5_update(&tctx, key, key_len); + fr_md5_final(tk, &tctx); + + key = tk; + key_len = 16; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + memset( k_ipad, 0, sizeof(k_ipad)); + memset( k_opad, 0, sizeof(k_opad)); + memcpy( k_ipad, key, key_len); + memcpy( k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + fr_md5_init(&context); /* init context for 1st + * pass */ + fr_md5_update(&context, k_ipad, 64); /* start with inner pad */ + fr_md5_update(&context, text, text_len); /* then text of datagram */ + fr_md5_final(digest, &context); /* finish up 1st pass */ + /* + * perform outer MD5 + */ + fr_md5_init(&context); /* init context for 2nd + * pass */ + fr_md5_update(&context, k_opad, 64); /* start with outer pad */ + fr_md5_update(&context, digest, 16); /* then results of 1st + * hash */ + fr_md5_final(digest, &context); /* finish up 2nd pass */ +} +#endif /* HAVE_OPENSSL_EVP_H */ + +/* +Test Vectors (Trailing '\0' of a character string not included in test): + + key = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b + key_len = 16 bytes + data = "Hi There" + data_len = 8 bytes + digest = 0x9294727a3638bb1c13f48ef8158bfc9d + + key = "Jefe" + data = "what do ya want for nothing?" + data_len = 28 bytes + digest = 0x750c783e6ab0b503eaa86e310a5db738 + + key = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + key_len 16 bytes + data = 0xDDDDDDDDDDDDDDDDDDDD... + ..DDDDDDDDDDDDDDDDDDDD... + ..DDDDDDDDDDDDDDDDDDDD... + ..DDDDDDDDDDDDDDDDDDDD... + ..DDDDDDDDDDDDDDDDDDDD + data_len = 50 bytes + digest = 0x56be34521d144c88dbb8c733f0e8b3f6 +*/ + +#ifdef TESTING +/* + * cc -DTESTING -I ../include/ hmac.c md5.c -o hmac + * + * ./hmac Jefe "what do ya want for nothing?" + */ +int main(int argc, char **argv) +{ + uint8_t digest[16]; + char *key; + int key_len; + char *text; + int text_len; + int i; + + key = argv[1]; + key_len = strlen(key); + + text = argv[2]; + text_len = strlen(text); + + fr_hmac_md5(digest, text, text_len, key, key_len); + + for (i = 0; i < 16; i++) { + printf("%02x", digest[i]); + } + printf("\n"); + + exit(0); + return 0; +} + +#endif diff --git a/src/lib/hmacsha1.c b/src/lib/hmacsha1.c new file mode 100644 index 0000000..8711f98 --- /dev/null +++ b/src/lib/hmacsha1.c @@ -0,0 +1,228 @@ +/* + * Adapted from hmac.c (HMAC-MD5) for use by SHA1. + * by <mcr@sandelman.ottawa.on.ca>. Test cases from RFC2202. + * + */ + +/* +** Function: hmac_sha1 +*/ + +RCSID("$Id$") + +#ifdef HAVE_OPENSSL_EVP_H +#include <openssl/hmac.h> +#include <openssl/evp.h> +#include <freeradius-devel/openssl3.h> +#endif + +#include <freeradius-devel/libradius.h> + +#ifdef HMAC_SHA1_DATA_PROBLEMS +unsigned int sha1_data_problems = 0; +#endif + +#ifdef HAVE_OPENSSL_EVP_H +/** Calculate HMAC using OpenSSL's SHA1 implementation + * + * @param digest Caller digest to be filled in. + * @param text Pointer to data stream. + * @param text_len length of data stream. + * @param key Pointer to authentication key. + * @param key_len Length of authentication key. + + */ +void fr_hmac_sha1(uint8_t digest[SHA1_DIGEST_LENGTH], uint8_t const *text, size_t text_len, + uint8_t const *key, size_t key_len) +{ + HMAC_CTX *ctx = HMAC_CTX_new(); + unsigned int len = SHA1_DIGEST_LENGTH; + + HMAC_Init_ex(ctx, key, key_len, EVP_sha1(), NULL); + HMAC_Update(ctx, text, text_len); + HMAC_Final(ctx, digest, &len); + HMAC_CTX_free(ctx); +} + +#else + +/** Calculate HMAC using internal SHA1 implementation + * + * @param digest Caller digest to be filled in. + * @param text Pointer to data stream. + * @param text_len length of data stream. + * @param key Pointer to authentication key. + * @param key_len Length of authentication key. + */ +void fr_hmac_sha1(uint8_t digest[SHA1_DIGEST_LENGTH], uint8_t const *text, size_t text_len, + uint8_t const *key, size_t key_len) +{ + fr_sha1_ctx context; + uint8_t k_ipad[65]; /* inner padding - key XORd with ipad */ + uint8_t k_opad[65]; /* outer padding - key XORd with opad */ + uint8_t tk[20]; + int i; + /* if key is longer than 64 bytes reset it to key=SHA1(key) */ + if (key_len > 64) { + + fr_sha1_ctx tctx; + + fr_sha1_init(&tctx); + fr_sha1_update(&tctx, key, key_len); + fr_sha1_final(tk, &tctx); + + key = tk; + key_len = 20; + } + +#ifdef HMAC_SHA1_DATA_PROBLEMS + if(sha1_data_problems) + { + int j,k; + + printf("\nhmac-sha1 key(%d): ", key_len); + j=0; k=0; + for (i = 0; i < key_len; i++) { + if(j==4) { + printf("_"); + j=0; + } + j++; + + printf("%02x", key[i]); + } + printf("\nDATA: (%d) ",text_len); + + j=0; k=0; + for (i = 0; i < text_len; i++) { + if(k==20) { + printf("\n "); + k=0; + j=0; + } + if(j==4) { + printf("_"); + j=0; + } + k++; + j++; + + printf("%02x", text[i]); + } + printf("\n"); + } +#endif + + + /* + * the HMAC_SHA1 transform looks like: + * + * SHA1(K XOR opad, SHA1(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + memset(k_ipad, 0, sizeof(k_ipad)); + memset(k_opad, 0, sizeof(k_opad)); + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* + * perform inner SHA1 + */ + fr_sha1_init(&context); /* init context for 1st pass */ + fr_sha1_update(&context, k_ipad, 64); /* start with inner pad */ + fr_sha1_update(&context, text, text_len); /* then text of datagram */ + fr_sha1_final(digest, &context); /* finish up 1st pass */ + /* + * perform outer SHA1 + */ + fr_sha1_init(&context); /* init context for 2nd pass */ + fr_sha1_update(&context, k_opad, 64); /* start with outer pad */ + fr_sha1_update(&context, digest, 20); /* then results of 1st hash */ + fr_sha1_final(digest, &context); /* finish up 2nd pass */ + +#ifdef HMAC_SHA1_DATA_PROBLEMS + if (sha1_data_problems) { + int j; + + printf("\nhmac-sha1 mac(20): "); + j=0; + for (i = 0; i < 20; i++) { + if(j==4) { + printf("_"); + j=0; + } + j++; + + printf("%02x", digest[i]); + } + printf("\n"); + } +#endif +} +#endif /* HAVE_OPENSSL_EVP_H */ + +/* +Test Vectors (Trailing '\0' of a character string not included in test): + + key = "Jefe" + data = "what do ya want for nothing?" + data_len = 28 bytes + digest = effcdf6ae5eb2fa2d27416d5f184df9c259a7c79 + + key = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + key_len 16 bytes + data = 0xDDDDDDDDDDDDDDDDDDDD... + ..DDDDDDDDDDDDDDDDDDDD... + ..DDDDDDDDDDDDDDDDDDDD... + ..DDDDDDDDDDDDDDDDDDDD... + ..DDDDDDDDDDDDDDDDDDDD + data_len = 50 bytes + digest = 0x56be34521d144c88dbb8c733f0e8b3f6 +*/ + +#ifdef TESTING +/* + * cc -DTESTING -I ../include/ hmac.c sha1.c -o hmac + * + * ./hmac Jefe "what do ya want for nothing?" + */ +int main(int argc, char **argv) +{ + uint8_t digest[20]; + char *key; + int key_len; + char *text; + int text_len; + int i; + + key = argv[1]; + key_len = strlen(key); + + text = argv[2]; + text_len = strlen(text); + + fr_hmac_sha1(digest, text, text_len, key, key_len); + + for (i = 0; i < 20; i++) { + printf("%02x", digest[i]); + } + printf("\n"); + + exit(0); + return 0; +} + +#endif diff --git a/src/lib/isaac.c b/src/lib/isaac.c new file mode 100644 index 0000000..fff1a35 --- /dev/null +++ b/src/lib/isaac.c @@ -0,0 +1,133 @@ +/* +------------------------------------------------------------------------------ +http://burtleburtle.net/bob/rand/isaac.html +rand.c: By Bob Jenkins. My random number generator, ISAAC. Public Domain +MODIFIED: + 960327: Creation (addition of randinit, really) + 970719: use context, not global variables, for internal state + 980324: make a portable version + 010626: Note this is public domain +------------------------------------------------------------------------------ +*/ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#define RANDSIZL (8) /* I recommend 8 for crypto, 4 for simulations */ +#define RANDSIZ (1<<RANDSIZL) + +#define ind(mm,x) ((mm)[(x>>2)&(RANDSIZ-1)]) +#define rngstep(mix,a,b,mm,m,m2,r,x) \ +{ \ + x = *m; \ + a = ((a^(mix)) + *(m2++)) & 0xffffffff; \ + *(m++) = y = (ind(mm,x) + a + b) & 0xffffffff; \ + *(r++) = b = (ind(mm,y>>RANDSIZL) + x) & 0xffffffff; \ +} + +void fr_isaac(fr_randctx *ctx) +{ + register uint32_t a,b,x,y,*m,*mm,*m2,*r,*mend; + mm=ctx->randmem; r=ctx->randrsl; + a = ctx->randa; b = (ctx->randb + (++ctx->randc)) & 0xffffffff; + for (m = mm, mend = m2 = m+(RANDSIZ/2); m<mend; ) + { + rngstep( a<<13, a, b, mm, m, m2, r, x); + rngstep( a>>6 , a, b, mm, m, m2, r, x); + rngstep( a<<2 , a, b, mm, m, m2, r, x); + rngstep( a>>16, a, b, mm, m, m2, r, x); + } + for (m2 = mm; m2<mend; ) + { + rngstep( a<<13, a, b, mm, m, m2, r, x); + rngstep( a>>6 , a, b, mm, m, m2, r, x); + rngstep( a<<2 , a, b, mm, m, m2, r, x); + rngstep( a>>16, a, b, mm, m, m2, r, x); + } + ctx->randb = b; ctx->randa = a; +} + + +#define mix(a,b,c,d,e,f,g,h) \ +{ \ + a^=b<<11; d+=a; b+=c; \ + b^=c>>2; e+=b; c+=d; \ + c^=d<<8; f+=c; d+=e; \ + d^=e>>16; g+=d; e+=f; \ + e^=f<<10; h+=e; f+=g; \ + f^=g>>4; a+=f; g+=h; \ + g^=h<<8; b+=g; h+=a; \ + h^=a>>9; c+=h; a+=b; \ +} + +/* if (flag==1), then use the contents of randrsl[] to initialize mm[]. */ +void fr_randinit(fr_randctx *ctx, int flag) +{ + int i; + uint32_t a,b,c,d,e,f,g,h; + uint32_t *m,*r; + ctx->randa = ctx->randb = ctx->randc = 0; + m=ctx->randmem; + r=ctx->randrsl; + a=b=c=d=e=f=g=h=0x9e3779b9; /* the golden ratio */ + + for (i=0; i<4; ++i) { /* scramble it */ + mix(a,b,c,d,e,f,g,h); + } + + if (flag) { + /* initialize using the contents of r[] as the seed */ + for (i=0; i<RANDSIZ; i+=8) { + a+=r[i ]; b+=r[i+1]; c+=r[i+2]; d+=r[i+3]; + e+=r[i+4]; f+=r[i+5]; g+=r[i+6]; h+=r[i+7]; + mix(a,b,c,d,e,f,g,h); + m[i ]=a; m[i+1]=b; m[i+2]=c; m[i+3]=d; + m[i+4]=e; m[i+5]=f; m[i+6]=g; m[i+7]=h; + } + /* do a second pass to make all of the seed affect all of m */ + for (i=0; i<RANDSIZ; i+=8) { + a+=m[i ]; b+=m[i+1]; c+=m[i+2]; d+=m[i+3]; + e+=m[i+4]; f+=m[i+5]; g+=m[i+6]; h+=m[i+7]; + mix(a,b,c,d,e,f,g,h); + m[i ]=a; m[i+1]=b; m[i+2]=c; m[i+3]=d; + m[i+4]=e; m[i+5]=f; m[i+6]=g; m[i+7]=h; + } + } else { + for (i=0; i<RANDSIZ; i+=8) { + /* fill in mm[] with messy stuff */ + mix(a,b,c,d,e,f,g,h); + m[i ]=a; m[i+1]=b; m[i+2]=c; m[i+3]=d; + m[i+4]=e; m[i+5]=f; m[i+6]=g; m[i+7]=h; + } + } + + fr_isaac(ctx); /* fill in the first set of results */ + ctx->randcnt=RANDSIZ; /* prepare to use the first set of results */ +} + + +#ifdef TEST +/* + * For testing. Output should be the same as + * + * http://burtleburtle.net/bob/rand/randvect.txt + */ +int main() +{ + uint32_t i,j; + fr_randctx ctx; + + ctx.randa = ctx.randb = ctx.randc = (uint32_t)0; + + for (i=0; i<256; ++i) ctx.randrsl[i]=(uint32_t)0; + fr_randinit(&ctx, 1); + for (i=0; i<2; ++i) { + fr_isaac(&ctx); + for (j=0; j<256; ++j) { + printf("%.8lx",ctx.randrsl[j]); + if ((j&7)==7) printf("\n"); + } + } +} +#endif diff --git a/src/lib/log.c b/src/lib/log.c new file mode 100644 index 0000000..c7e3256 --- /dev/null +++ b/src/lib/log.c @@ -0,0 +1,343 @@ +/* + * log.c Functions in the library call radlib_log() which + * does internal logging. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +/* + * Are we using glibc or a close relative? + */ +#ifdef HAVE_FEATURES_H +# include <features.h> +#endif + +#define FR_STRERROR_BUFSIZE (2048) + +fr_thread_local_setup(char *, fr_strerror_buffer) /* macro */ +fr_thread_local_setup(char *, fr_syserror_buffer) /* macro */ + +#ifndef NDEBUG +/** POSIX-2008 errno macros + * + * Non-POSIX macros may be added, but you must check they're defined. + */ +static char const *fr_errno_macro_names[] = { + [E2BIG] = "E2BIG", + [EACCES] = "EACCES", + [EADDRINUSE] = "EADDRINUSE", + [EADDRNOTAVAIL] = "EADDRNOTAVAIL", + [EAFNOSUPPORT] = "EAFNOSUPPORT", +#if EWOULDBLOCK == EAGAIN + [EWOULDBLOCK] = "EWOULDBLOCK or EAGAIN", +#else + [EAGAIN] = "EAGAIN", + [EWOULDBLOCK] = "EWOULDBLOCK", +#endif + [EALREADY] = "EALREADY", + [EBADF] = "EBADF", + [EBADMSG] = "EBADMSG", + [EBUSY] = "EBUSY", + [ECANCELED] = "ECANCELED", + [ECHILD] = "ECHILD", + [ECONNABORTED] = "ECONNABORTED", + [ECONNREFUSED] = "ECONNREFUSED", + [ECONNRESET] = "ECONNRESET", + [EDEADLK] = "EDEADLK", + [EDESTADDRREQ] = "EDESTADDRREQ", + [EDOM] = "EDOM", + [EDQUOT] = "EDQUOT", + [EEXIST] = "EEXIST", + [EFAULT] = "EFAULT", + [EFBIG] = "EFBIG", + [EHOSTUNREACH] = "EHOSTUNREACH", + [EIDRM] = "EIDRM", + [EILSEQ] = "EILSEQ", + [EINPROGRESS] = "EINPROGRESS", + [EINTR] = "EINTR", + [EINVAL] = "EINVAL", + [EIO] = "EIO", + [EISCONN] = "EISCONN", + [EISDIR] = "EISDIR", + [ELOOP] = "ELOOP", + [EMFILE] = "EMFILE", + [EMLINK] = "EMLINK", + [EMSGSIZE] = "EMSGSIZE", + [EMULTIHOP] = "EMULTIHOP", + [ENAMETOOLONG] = "ENAMETOOLONG", + [ENETDOWN] = "ENETDOWN", + [ENETRESET] = "ENETRESET", + [ENETUNREACH] = "ENETUNREACH", + [ENFILE] = "ENFILE", + [ENOBUFS] = "ENOBUFS", +#ifdef ENODATA + [ENODATA] = "ENODATA", +#endif + [ENODEV] = "ENODEV", + [ENOENT] = "ENOENT", + [ENOEXEC] = "ENOEXEC", + [ENOLCK] = "ENOLCK", + [ENOLINK] = "ENOLINK", + [ENOMEM] = "ENOMEM", + [ENOMSG] = "ENOMSG", + [ENOPROTOOPT] = "ENOPROTOOPT", + [ENOSPC] = "ENOSPC", +#ifdef ENOSR + [ENOSR] = "ENOSR", +#endif +#ifdef ENOSTR + [ENOSTR] = "ENOSTR", +#endif + [ENOSYS] = "ENOSYS", + [ENOTCONN] = "ENOTCONN", + [ENOTDIR] = "ENOTDIR", + [ENOTEMPTY] = "ENOTEMPTY", +#ifdef ENOTRECOVERABLE + [ENOTRECOVERABLE] = "ENOTRECOVERABLE", +#endif + [ENOTSOCK] = "ENOTSOCK", + [ENOTSUP] = "ENOTSUP", +#if ENOTSUP != EOPNOTSUPP + [EOPNOTSUPP] = "EOPNOTSUPP", +#endif + [ENOTTY] = "ENOTTY", + [ENXIO] = "ENXIO", + [EOVERFLOW] = "EOVERFLOW", +#ifdef EOWNERDEAD + [EOWNERDEAD] = "EOWNERDEAD", +#endif + [EPERM] = "EPERM", + [EPIPE] = "EPIPE", + [EPROTO] = "EPROTO", + [EPROTONOSUPPORT] = "EPROTONOSUPPORT", + [EPROTOTYPE] = "EPROTOTYPE", + [ERANGE] = "ERANGE", + [EROFS] = "EROFS", + [ESPIPE] = "ESPIPE", + [ESRCH] = "ESRCH", + [ESTALE] = "ESTALE", +#ifdef ETIME + [ETIME] = "ETIME", +#endif + [ETIMEDOUT] = "ETIMEDOUT", + [ETXTBSY] = "ETXTBSY", + [EXDEV] = "EXDEV" +}; +#endif + +/* + * Explicitly cleanup the memory allocated to the error buffer, + * just in case valgrind complains about it. + */ +static void _fr_logging_free(void *arg) +{ + free(arg); +} + +/** Log to thread local error buffer + * + * @param fmt printf style format string. If NULL sets the 'new' byte to false, + * effectively clearing the last message. + */ +void fr_strerror_printf(char const *fmt, ...) +{ + va_list ap; + + char *buffer; + + buffer = fr_thread_local_init(fr_strerror_buffer, _fr_logging_free); + if (!buffer) { + int ret; + + /* + * malloc is thread safe, talloc is not + */ + buffer = calloc((FR_STRERROR_BUFSIZE * 2) + 1, sizeof(char)); /* One byte extra for status */ + if (!buffer) { + fr_perror("Failed allocating memory for libradius error buffer"); + return; + } + + ret = fr_thread_local_set(fr_strerror_buffer, buffer); + if (ret != 0) { + fr_perror("Failed setting up thread-local storage for libradius error buffer: %s", fr_syserror(ret)); + free(buffer); + return; + } + } + + /* + * NULL has a special meaning, setting the new bit to false. + */ + if (!fmt) { + buffer[FR_STRERROR_BUFSIZE * 2] &= 0x06; + return; + } + + va_start(ap, fmt); + /* + * Alternate where we write the message, so we can do: + * fr_strerror_printf("Additional error: %s", fr_strerror()); + */ + switch (buffer[FR_STRERROR_BUFSIZE * 2] & 0x06) { + default: + vsnprintf(buffer + FR_STRERROR_BUFSIZE, FR_STRERROR_BUFSIZE, fmt, ap); + buffer[FR_STRERROR_BUFSIZE * 2] = 0x05; /* Flip the 'new' bit to true */ + break; + + case 0x04: + vsnprintf(buffer, FR_STRERROR_BUFSIZE, fmt, ap); + buffer[FR_STRERROR_BUFSIZE * 2] = 0x03; /* Flip the 'new' bit to true */ + break; + } + va_end(ap); +} + +/** Get the last library error + * + * Will only return the last library error once, after which it will return a zero length string. + * + * @return library error or zero length string + */ +char const *fr_strerror(void) +{ + char *buffer; + + buffer = fr_thread_local_get(fr_strerror_buffer); + if (!buffer) return ""; + + switch (buffer[FR_STRERROR_BUFSIZE * 2]) { + default: + return ""; + + case 0x03: + buffer[FR_STRERROR_BUFSIZE * 2] &= 0x06; /* Flip the 'new' bit to false */ + return buffer; + + case 0x05: + buffer[FR_STRERROR_BUFSIZE * 2] &= 0x06; /* Flip the 'new' bit to false */ + return buffer + FR_STRERROR_BUFSIZE; + } +} + +/** Guaranteed to be thread-safe version of strerror + * + * @param num errno as returned by function or from global errno. + * @return local specific error string relating to errno. + */ +char const *fr_syserror(int num) +{ + char *buffer, *p, *end; + int ret; + + buffer = fr_thread_local_init(fr_syserror_buffer, _fr_logging_free); + if (!buffer) { + /* + * malloc is thread safe, talloc is not + */ + buffer = malloc(sizeof(char) * FR_STRERROR_BUFSIZE); + if (!buffer) { + fr_perror("Failed allocating memory for system error buffer"); + return NULL; + } + + ret = fr_thread_local_set(fr_syserror_buffer, buffer); + if (ret != 0) { + fr_perror("Failed setting up thread-local storage for system error buffer"); + free(buffer); + return NULL; + } + } + + if (!num) return "No error"; + + p = buffer; + end = p + FR_STRERROR_BUFSIZE; + +#ifndef NDEBUG + /* + * Prefix system errors with the macro name and number + * if we're debugging. + */ + if (num < (int)(sizeof(fr_errno_macro_names) / sizeof(*fr_errno_macro_names))) { + p += snprintf(p, end - p, "%s: ", fr_errno_macro_names[num]); + } else { + p += snprintf(p, end - p, "errno %i: ", num); + } + if (p >= end) return p; +#endif + + /* + * XSI-Compliant version + */ +#if !defined(HAVE_FEATURES_H) || !defined(__GLIBC__) || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 500) && ! _GNU_SOURCE) + ret = strerror_r(num, p, end - p); + if (ret != 0) { +# ifndef NDEBUG + fprintf(stderr, "strerror_r() failed to write error for errno %i to buffer %p (%zu bytes), " + "returned %i: %s\n", num, buffer, (size_t) FR_STRERROR_BUFSIZE, ret, strerror(ret)); +# endif + buffer[0] = '\0'; + } + return buffer; + /* + * GNU Specific version + * + * The GNU Specific version returns a char pointer. That pointer may point + * the buffer you just passed in, or to an immutable static string. + */ +#else + { + p = strerror_r(num, p, end - p); + if (!p) { +# ifndef NDEBUG + fprintf(stderr, "strerror_r() failed to write error for errno %i to buffer %p " + "(%zu bytes): %s\n", num, buffer, (size_t) FR_STRERROR_BUFSIZE, strerror(errno)); +# endif + buffer[0] = '\0'; + return buffer; + } + + return p; + } +#endif + +} + +void fr_perror(char const *fmt, ...) +{ + char const *error; + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + + error = fr_strerror(); + if (error && (error[0] != '\0')) { + fprintf(stderr, ": %s\n", error); + } else { + fputs("\n", stderr); + } + + va_end(ap); +} diff --git a/src/lib/md4.c b/src/lib/md4.c new file mode 100644 index 0000000..7169000 --- /dev/null +++ b/src/lib/md4.c @@ -0,0 +1,310 @@ +/** + * $Id$ + * + * @note license is LGPL, but largely derived from a public domain source. + * + * @file md4.c + * @brief md4 digest functions. + */ + +RCSID("$Id$") + +/* + * FORCE MD4 TO USE OUR MD4 HEADER FILE! + * If we don't do this, it might pick up the systems broken MD4. + */ +#include <freeradius-devel/md4.h> + +/** Calculate the MD4 hash of the contents of a buffer + * + * @param[out] out Where to write the MD4 digest. Must be a minimum of MD4_DIGEST_LENGTH. + * @param[in] in Data to hash. + * @param[in] inlen Length of the data. + */ +void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen) +{ + FR_MD4_CTX ctx; + + fr_md4_init(&ctx); + fr_md4_update(&ctx, in, inlen); + fr_md4_final(out, &ctx); + fr_md4_destroy(&ctx); +} + +#ifndef HAVE_OPENSSL_MD4_H +/* + * This code implements the MD4 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * Todd C. Miller modified the MD5 code to do MD4 based on RFC 1186. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD4Context structure, pass it to fr_md4_init, call fr_md4_update as + * needed on buffers full of bytes, and then call fr_md4_final, which + * will fill a supplied 16-byte array with the digest. + */ + +#ifdef FR_LITTLE_ENDIAN +# define htole32_4(buf) /* Nothing */ +# define htole32_14(buf) /* Nothing */ +# define htole32_16(buf) /* Nothing */ +#else +/* Sometimes defined by endian.h */ +# ifndef htole32 +# define htole32(x)\ + (((((uint32_t)x) & 0xff000000) >> 24) |\ + ((((uint32_t)x) & 0x00ff0000) >> 8) |\ + ((((uint32_t)x) & 0x0000ff00) << 8) |\ + ((((uint32_t)x) & 0x000000ff) << 24)) +# endif +# define htole32_4(buf) do {\ + (buf)[0] = htole32((buf)[0]);\ + (buf)[1] = htole32((buf)[1]);\ + (buf)[2] = htole32((buf)[2]);\ + (buf)[3] = htole32((buf)[3]);\ +} while (0) + +# define htole32_14(buf) do {\ + (buf)[0] = htole32((buf)[0]);\ + (buf)[1] = htole32((buf)[1]);\ + (buf)[2] = htole32((buf)[2]);\ + (buf)[3] = htole32((buf)[3]);\ + (buf)[4] = htole32((buf)[4]);\ + (buf)[5] = htole32((buf)[5]);\ + (buf)[6] = htole32((buf)[6]);\ + (buf)[7] = htole32((buf)[7]);\ + (buf)[8] = htole32((buf)[8]);\ + (buf)[9] = htole32((buf)[9]);\ + (buf)[10] = htole32((buf)[10]);\ + (buf)[11] = htole32((buf)[11]);\ + (buf)[12] = htole32((buf)[12]);\ + (buf)[13] = htole32((buf)[13]);\ +} while (0) + +# define htole32_16(buf) do {\ + (buf)[0] = htole32((buf)[0]);\ + (buf)[1] = htole32((buf)[1]);\ + (buf)[2] = htole32((buf)[2]);\ + (buf)[3] = htole32((buf)[3]);\ + (buf)[4] = htole32((buf)[4]);\ + (buf)[5] = htole32((buf)[5]);\ + (buf)[6] = htole32((buf)[6]);\ + (buf)[7] = htole32((buf)[7]);\ + (buf)[8] = htole32((buf)[8]);\ + (buf)[9] = htole32((buf)[9]);\ + (buf)[10] = htole32((buf)[10]);\ + (buf)[11] = htole32((buf)[11]);\ + (buf)[12] = htole32((buf)[12]);\ + (buf)[13] = htole32((buf)[13]);\ + (buf)[14] = htole32((buf)[14]);\ + (buf)[15] = htole32((buf)[15]);\ +} while (0) +#endif + +/** Initialise a new MD4 context + * + * Set bit count to 0 and buffer to mysterious initialization constants. + * + * @param[out] ctx to initialise. + */ +void fr_md4_init(FR_MD4_CTX *ctx) +{ + ctx->count[0] = 0; + ctx->count[1] = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; +} + +/** Feed additional data into the MD4 hashing function + * + * @param[in,out] ctx to update. + * @param[in] in Data to hash. + * @param[in] inlen Length of the data. + */ +void fr_md4_update(FR_MD4_CTX *ctx, uint8_t const *in, size_t inlen) +{ + uint32_t count; + + /* Bytes already stored in ctx->buffer */ + count = (uint32_t)((ctx->count[0] >> 3) & 0x3f); + + /* Update bitcount */ +/* ctx->count += (uint64_t)inlen << 3;*/ + if ((ctx->count[0] += ((uint32_t)inlen << 3)) < (uint32_t)inlen) { + /* Overflowed ctx->count[0] */ + ctx->count[1]++; + } + ctx->count[1] += ((uint32_t)inlen >> 29); + + /* Handle any leading odd-sized chunks */ + if (count) { + unsigned char *p = (unsigned char *)ctx->buffer + count; + + count = MD4_BLOCK_LENGTH - count; + if (inlen < count) { + memcpy(p, in, inlen); + return; + } + memcpy(p, in, count); + htole32_16((uint32_t *)ctx->buffer); + fr_md4_transform(ctx->state, ctx->buffer); + in += count; + inlen -= count; + } + + /* Process data in MD4_BLOCK_LENGTH-byte chunks */ + while (inlen >= MD4_BLOCK_LENGTH) { + memcpy(ctx->buffer, in, MD4_BLOCK_LENGTH); + htole32_16((uint32_t *)ctx->buffer); + fr_md4_transform(ctx->state, ctx->buffer); + in += MD4_BLOCK_LENGTH; + inlen -= MD4_BLOCK_LENGTH; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->buffer, in, inlen); +} + +/** Finalise the MD4 context and write out the hash + * + * Final wrapup - pad to 64-byte boundary with the bit pattern 1 0* + * (64-bit count of bits processed, MSB-first). + * + * @param[out] out Where to write the MD4 digest. Minimum length of MD4_DIGEST_LENGTH. + * @param[in,out] ctx to finalise. + */ +void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx) +{ + uint32_t count; + unsigned char *p; + + /* number of bytes mod 64 */ + count = (uint32_t)(ctx->count[0] >> 3) & 0x3f; + + /* + * Set the first char of padding to 0x80. + * This is safe since there is always at least one byte free. + */ + p = ctx->buffer + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + htole32_16((uint32_t *)ctx->buffer); + fr_md4_transform(ctx->state, ctx->buffer); + + /* Now fill the next block with 56 bytes */ + memset(ctx->buffer, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + htole32_14((uint32_t *)ctx->buffer); + + /* Append bit count and transform */ + ((uint32_t *)ctx->buffer)[14] = ctx->count[0]; + ((uint32_t *)ctx->buffer)[15] = ctx->count[1]; + + fr_md4_transform(ctx->state, ctx->buffer); + htole32_4(ctx->state); + memcpy(out, ctx->state, MD4_DIGEST_LENGTH); + memset(ctx, 0, sizeof(*ctx)); /* in case it's sensitive */ +} + +/* The three core functions - F1 is optimized somewhat */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) ((x & y) | (x & z) | (y & z)) +#define F3(x, y, z) (x ^ y ^ z) + +/* This is the central step in the MD4 algorithm. */ +#define MD4STEP(f, w, x, y, z, data, s) (w += f(x, y, z) + data, w = w << s | w >> (32 - s)) + +/** The core of the MD4 algorithm + * + * This alters an existing MD4 hash to reflect the addition of 16 + * longwords of new data. fr_md4_update blocks the data and converts bytes + * into longwords for this routine. + * + * @param[in] state 16 bytes of data to feed into the hashing function. + * @param[in,out] block MD4 digest block to update. + */ +void fr_md4_transform(uint32_t state[4], uint8_t const block[MD4_BLOCK_LENGTH]) +{ + uint32_t a, b, c, d; + uint32_t const *in = (uint32_t const *)block; + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + MD4STEP(F1, a, b, c, d, in[ 0], 3); + MD4STEP(F1, d, a, b, c, in[ 1], 7); + MD4STEP(F1, c, d, a, b, in[ 2], 11); + MD4STEP(F1, b, c, d, a, in[ 3], 19); + MD4STEP(F1, a, b, c, d, in[ 4], 3); + MD4STEP(F1, d, a, b, c, in[ 5], 7); + MD4STEP(F1, c, d, a, b, in[ 6], 11); + MD4STEP(F1, b, c, d, a, in[ 7], 19); + MD4STEP(F1, a, b, c, d, in[ 8], 3); + MD4STEP(F1, d, a, b, c, in[ 9], 7); + MD4STEP(F1, c, d, a, b, in[10], 11); + MD4STEP(F1, b, c, d, a, in[11], 19); + MD4STEP(F1, a, b, c, d, in[12], 3); + MD4STEP(F1, d, a, b, c, in[13], 7); + MD4STEP(F1, c, d, a, b, in[14], 11); + MD4STEP(F1, b, c, d, a, in[15], 19); + + MD4STEP(F2, a, b, c, d, in[ 0] + 0x5a827999, 3); + MD4STEP(F2, d, a, b, c, in[ 4] + 0x5a827999, 5); + MD4STEP(F2, c, d, a, b, in[ 8] + 0x5a827999, 9); + MD4STEP(F2, b, c, d, a, in[12] + 0x5a827999, 13); + MD4STEP(F2, a, b, c, d, in[ 1] + 0x5a827999, 3); + MD4STEP(F2, d, a, b, c, in[ 5] + 0x5a827999, 5); + MD4STEP(F2, c, d, a, b, in[ 9] + 0x5a827999, 9); + MD4STEP(F2, b, c, d, a, in[13] + 0x5a827999, 13); + MD4STEP(F2, a, b, c, d, in[ 2] + 0x5a827999, 3); + MD4STEP(F2, d, a, b, c, in[ 6] + 0x5a827999, 5); + MD4STEP(F2, c, d, a, b, in[10] + 0x5a827999, 9); + MD4STEP(F2, b, c, d, a, in[14] + 0x5a827999, 13); + MD4STEP(F2, a, b, c, d, in[ 3] + 0x5a827999, 3); + MD4STEP(F2, d, a, b, c, in[ 7] + 0x5a827999, 5); + MD4STEP(F2, c, d, a, b, in[11] + 0x5a827999, 9); + MD4STEP(F2, b, c, d, a, in[15] + 0x5a827999, 13); + + MD4STEP(F3, a, b, c, d, in[ 0] + 0x6ed9eba1, 3); + MD4STEP(F3, d, a, b, c, in[ 8] + 0x6ed9eba1, 9); + MD4STEP(F3, c, d, a, b, in[ 4] + 0x6ed9eba1, 11); + MD4STEP(F3, b, c, d, a, in[12] + 0x6ed9eba1, 15); + MD4STEP(F3, a, b, c, d, in[ 2] + 0x6ed9eba1, 3); + MD4STEP(F3, d, a, b, c, in[10] + 0x6ed9eba1, 9); + MD4STEP(F3, c, d, a, b, in[ 6] + 0x6ed9eba1, 11); + MD4STEP(F3, b, c, d, a, in[14] + 0x6ed9eba1, 15); + MD4STEP(F3, a, b, c, d, in[ 1] + 0x6ed9eba1, 3); + MD4STEP(F3, d, a, b, c, in[ 9] + 0x6ed9eba1, 9); + MD4STEP(F3, c, d, a, b, in[ 5] + 0x6ed9eba1, 11); + MD4STEP(F3, b, c, d, a, in[13] + 0x6ed9eba1, 15); + MD4STEP(F3, a, b, c, d, in[ 3] + 0x6ed9eba1, 3); + MD4STEP(F3, d, a, b, c, in[11] + 0x6ed9eba1, 9); + MD4STEP(F3, c, d, a, b, in[ 7] + 0x6ed9eba1, 11); + MD4STEP(F3, b, c, d, a, in[15] + 0x6ed9eba1, 15); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} +#endif diff --git a/src/lib/md5.c b/src/lib/md5.c new file mode 100644 index 0000000..b5c1729 --- /dev/null +++ b/src/lib/md5.c @@ -0,0 +1,276 @@ +/** + * $Id$ + * + * @note license is LGPL, but largely derived from a public domain source. + * + * @file md5.c + * @brief md5 digest functions. + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +/* + * FORCE MD5 TO USE OUR MD5 HEADER FILE! + * If we don't do this, it might pick up the systems broken MD5. + */ +#include <freeradius-devel/md5.h> + +/** Calculate the MD5 hash of the contents of a buffer + * + * @param[out] out Where to write the MD5 digest. Must be a minimum of MD5_DIGEST_LENGTH. + * @param[in] in Data to hash. + * @param[in] inlen Length of the data. + */ +void fr_md5_calc(uint8_t *out, uint8_t const *in, size_t inlen) +{ + FR_MD5_CTX ctx; + + fr_md5_init(&ctx); + fr_md5_update(&ctx, in, inlen); + fr_md5_final(out, &ctx); + fr_md5_destroy(&ctx); +} + +#ifndef HAVE_OPENSSL_MD5_H +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to fr_md5_init, call fr_md5_update as + * needed on buffers full of bytes, and then call fr_md5_final, which + * will fill a supplied 16-byte array with the digest. + */ +#define PUT_64BIT_LE(cp, value) do {\ + (cp)[7] = (value)[1] >> 24;\ + (cp)[6] = (value)[1] >> 16;\ + (cp)[5] = (value)[1] >> 8;\ + (cp)[4] = (value)[1];\ + (cp)[3] = (value)[0] >> 24;\ + (cp)[2] = (value)[0] >> 16;\ + (cp)[1] = (value)[0] >> 8;\ + (cp)[0] = (value)[0];\ +} while (0) + +#define PUT_32BIT_LE(cp, value) do {\ + (cp)[3] = (value) >> 24;\ + (cp)[2] = (value) >> 16;\ + (cp)[1] = (value) >> 8;\ + (cp)[0] = (value);\ +} while (0) + +static const uint8_t PADDING[MD5_BLOCK_LENGTH] = { + 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 +}; + +/** Initialise a new MD5 context + * + * Set bit count to 0 and buffer to mysterious initialization constants. + * + * @param[out] ctx to initialise. + */ +void fr_md5_init(FR_MD5_CTX *ctx) +{ + ctx->count[0] = 0; + ctx->count[1] = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; +} + +/** Feed additional data into the MD5 hashing function + * + * @param[in,out] ctx to update. + * @param[in] in Data to hash. + * @param[in] inlen Length of the data. + */ +void fr_md5_update(FR_MD5_CTX *ctx, uint8_t const *in, size_t inlen) +{ + size_t have, need; + + /* Check how many bytes we already have and how many more we need. */ + have = (size_t)((ctx->count[0] >> 3) & (MD5_BLOCK_LENGTH - 1)); + need = MD5_BLOCK_LENGTH - have; + + /* Update bitcount */ +/* ctx->count += (uint64_t)inlen << 3;*/ + if ((ctx->count[0] += ((uint32_t)inlen << 3)) < (uint32_t)inlen) { + /* Overflowed ctx->count[0] */ + ctx->count[1]++; + } + ctx->count[1] += ((uint32_t)inlen >> 29); + + if (inlen >= need) { + if (have != 0) { + memcpy(ctx->buffer + have, in, need); + fr_md5_transform(ctx->state, ctx->buffer); + in += need; + inlen -= need; + have = 0; + } + + /* Process data in MD5_BLOCK_LENGTH-byte chunks. */ + while (inlen >= MD5_BLOCK_LENGTH) { + fr_md5_transform(ctx->state, in); + in += MD5_BLOCK_LENGTH; + inlen -= MD5_BLOCK_LENGTH; + } + } + + /* Handle any remaining bytes of data. */ + if (inlen != 0) memcpy(ctx->buffer + have, in, inlen); +} + +/** Finalise the MD5 context and write out the hash + * + * Final wrapup - pad to 64-byte boundary with the bit pattern 1 0* + * (64-bit count of bits processed, MSB-first). + * + * @param[out] out Where to write the MD5 digest. Minimum length of MD5_DIGEST_LENGTH. + * @param[in,out] ctx to finalise. + */ +void fr_md5_final(uint8_t out[MD5_DIGEST_LENGTH], FR_MD5_CTX *ctx) +{ + uint8_t count[8]; + size_t padlen; + int i; + + /* Convert count to 8 bytes in little endian order. */ + PUT_64BIT_LE(count, ctx->count); + + /* Pad out to 56 mod 64. */ + padlen = MD5_BLOCK_LENGTH - + ((ctx->count[0] >> 3) & (MD5_BLOCK_LENGTH - 1)); + if (padlen < 1 + 8) + padlen += MD5_BLOCK_LENGTH; + fr_md5_update(ctx, PADDING, padlen - 8); /* padlen - 8 <= 64 */ + fr_md5_update(ctx, count, 8); + + if (out != NULL) { + for (i = 0; i < 4; i++) + PUT_32BIT_LE(out + i * 4, ctx->state[i]); + } + memset(ctx, 0, sizeof(*ctx)); /* in case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +/** The core of the MD5 algorithm + * + * This alters an existing MD5 hash to reflect the addition of 16 + * longwords of new data. fr_md5_update blocks the data and converts bytes + * into longwords for this routine. + * + * @param[in] state 16 bytes of data to feed into the hashing function. + * @param[in,out] block MD5 digest block to update. + */ +void fr_md5_transform(uint32_t state[4], uint8_t const block[MD5_BLOCK_LENGTH]) +{ + uint32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4]; + + for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) { + in[a] = (uint32_t)( + (uint32_t)(block[a * 4 + 0]) | + (uint32_t)(block[a * 4 + 1]) << 8 | + (uint32_t)(block[a * 4 + 2]) << 16 | + (uint32_t)(block[a * 4 + 3]) << 24); + } + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} +#endif diff --git a/src/lib/misc.c b/src/lib/misc.c new file mode 100644 index 0000000..b80b9ce --- /dev/null +++ b/src/lib/misc.c @@ -0,0 +1,2192 @@ +/* + * misc.c Various miscellaneous functions. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#include <ctype.h> +#include <sys/file.h> +#include <fcntl.h> +#include <grp.h> +#include <pwd.h> +#include <sys/uio.h> + +#ifdef HAVE_DIRENT_H +#include <dirent.h> + +/* + * Some versions of Linux don't have closefrom(), but they will + * have /proc. + * + * BSD systems will generally have closefrom(), but not proc. + * + * OSX doesn't have closefrom() or /proc/self/fd, but it does + * have /dev/fd + */ +#ifdef __linux__ +#define CLOSEFROM_DIR "/proc/self/fd" +#elif defined(__APPLE__) +#define CLOSEFROM_DIR "/dev/fd" +#else +#undef HAVE_DIRENT_H +#endif + +#endif + +#define FR_PUT_LE16(a, val)\ + do {\ + a[1] = ((uint16_t) (val)) >> 8;\ + a[0] = ((uint16_t) (val)) & 0xff;\ + } while (0) + +bool fr_dns_lookups = false; /* IP -> hostname lookups? */ +bool fr_hostname_lookups = true; /* hostname -> IP lookups? */ +int fr_debug_lvl = 0; + +static char const *months[] = { + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" }; + +fr_thread_local_setup(char *, fr_inet_ntop_buffer) /* macro */ + +typedef struct fr_talloc_link { + bool armed; + TALLOC_CTX *child; +} fr_talloc_link_t; + +/** Sets a signal handler using sigaction if available, else signal + * + * @param sig to set handler for. + * @param func handler to set. + */ +DIAG_OPTIONAL +DIAG_OFF(disabled-macro-expansion) +int fr_set_signal(int sig, sig_t func) +{ +#ifdef HAVE_SIGACTION + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + act.sa_handler = func; + + if (sigaction(sig, &act, NULL) < 0) { + fr_strerror_printf("Failed setting signal %i handler via sigaction(): %s", sig, fr_syserror(errno)); + return -1; + } +#else + if (signal(sig, func) < 0) { + fr_strerror_printf("Failed setting signal %i handler via signal(): %s", sig, fr_syserror(errno)); + return -1; + } +#endif + return 0; +} +DIAG_ON(disabled-macro-expansion) + +/** Uninstall a signal for a specific handler + * + * man sigaction says these are fine to call from a signal handler. + * + * @param sig SIGNAL + */ +DIAG_OPTIONAL +DIAG_OFF(disabled-macro-expansion) +int fr_unset_signal(int sig) +{ +#ifdef HAVE_SIGACTION + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + act.sa_handler = SIG_DFL; + + return sigaction(sig, &act, NULL); +#else + return signal(sig, SIG_DFL); +#endif +} +DIAG_ON(disabled-macro-expansion) + +static int _fr_trigger_talloc_ctx_free(fr_talloc_link_t *trigger) +{ + if (trigger->armed) talloc_free(trigger->child); + + return 0; +} + +static int _fr_disarm_talloc_ctx_free(bool **armed) +{ + **armed = false; + return 0; +} + +/** Link a parent and a child context, so the child is freed before the parent + * + * @note This is not thread safe. Do not free parent before threads are joined, do not call from a child thread. + * @note It's OK to free the child before threads are joined, but this will leak memory until the parent is freed. + * + * @param parent who's fate the child should share. + * @param child bound to parent's lifecycle. + * @return 0 on success -1 on failure. + */ +int fr_link_talloc_ctx_free(TALLOC_CTX *parent, TALLOC_CTX *child) +{ + fr_talloc_link_t *trigger; + bool **disarm; + + trigger = talloc(parent, fr_talloc_link_t); + if (!trigger) return -1; + + disarm = talloc(child, bool *); + if (!disarm) { + talloc_free(trigger); + return -1; + } + + trigger->child = child; + trigger->armed = true; + *disarm = &trigger->armed; + + talloc_set_destructor(trigger, _fr_trigger_talloc_ctx_free); + talloc_set_destructor(disarm, _fr_disarm_talloc_ctx_free); + + return 0; +} + +/* + * Explicitly cleanup the memory allocated to the error inet_ntop + * buffer. + */ +static void _fr_inet_ntop_free(void *arg) +{ + free(arg); +} + +/** Wrapper around inet_ntop, prints IPv4/IPv6 addresses + * + * inet_ntop requires the caller pass in a buffer for the address. + * This would be annoying and cumbersome, seeing as quite often the ASCII + * address is only used for logging output. + * + * So as with lib/log.c use TLS to allocate thread specific buffers, and + * write the IP address there instead. + * + * @param af address family, either AF_INET or AF_INET6. + * @param src pointer to network address structure. + * @return NULL on error, else pointer to ASCII buffer containing text version of address. + */ +char const *fr_inet_ntop(int af, void const *src) +{ + char *buffer; + + if (!src) { + return NULL; + } + + buffer = fr_thread_local_init(fr_inet_ntop_buffer, _fr_inet_ntop_free); + if (!buffer) { + int ret; + + /* + * malloc is thread safe, talloc is not + */ + buffer = malloc(sizeof(char) * INET6_ADDRSTRLEN); + if (!buffer) { + fr_perror("Failed allocating memory for inet_ntop buffer"); + return NULL; + } + + ret = fr_thread_local_set(fr_inet_ntop_buffer, buffer); + if (ret != 0) { + fr_perror("Failed setting up TLS for inet_ntop buffer: %s", fr_syserror(ret)); + free(buffer); + return NULL; + } + } + buffer[0] = '\0'; + + return inet_ntop(af, src, buffer, INET6_ADDRSTRLEN); +} + +/* + * Return an IP address in standard dot notation + * + * FIXME: DELETE THIS + */ +char const *ip_ntoa(char *buffer, uint32_t ipaddr) +{ + ipaddr = ntohl(ipaddr); + + sprintf(buffer, "%d.%d.%d.%d", + (ipaddr >> 24) & 0xff, + (ipaddr >> 16) & 0xff, + (ipaddr >> 8) & 0xff, + (ipaddr ) & 0xff); + return buffer; +} + +/* + * Parse decimal digits until we run out of decimal digits. + */ +static int ip_octet_from_str(char const *str, uint32_t *poctet) +{ + uint32_t octet; + char const *p = str; + + if ((*p < '0') || (*p > '9')) { + return -1; + } + + octet = 0; + + while ((*p >= '0') && (*p <= '9')) { + octet *= 10; + octet += *p - '0'; + p++; + + if (octet > 255) return -1; + } + + + *poctet = octet; + return p - str; +} + +static int ip_prefix_from_str(char const *str, uint32_t *paddr) +{ + int shift, length; + uint32_t octet; + uint32_t addr; + char const *p = str; + + addr = 0; + + for (shift = 24; shift >= 0; shift -= 8) { + length = ip_octet_from_str(p, &octet); + if (length <= 0) return -1; + + addr |= octet << shift; + p += length; + + /* + * EOS or / means we're done. + */ + if (!*p || (*p == '/')) break; + + /* + * We require dots between octets. + */ + if (*p != '.') return -1; + p++; + } + + *paddr = htonl(addr); + return p - str; +} + + +/** + * Parse an IPv4 address, IPv4 prefix in presentation format (and others), or + * a hostname. + * + * @param out Where to write the ip address value. + * @param value to parse, may be dotted quad [+ prefix], or integer, or octal number, or '*' (INADDR_ANY), or a hostname. + * @param inlen Length of value, if value is \0 terminated inlen may be -1. + * @param resolve If true and value doesn't look like an IP address, try and resolve value as a hostname. + * @param fallback to IPv6 resolution if no A records can be found. + * @return 0 if ip address was parsed successfully, else -1 on error. + */ +int fr_pton4(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, bool fallback) +{ + char *p; + unsigned int mask; + char *eptr; + + /* Dotted quad + / + [0-9]{1,2} or a hostname (RFC1035 2.3.4 Size limits) */ + char buffer[256]; + + /* + * Copy to intermediary buffer if we were given a length + */ + if (inlen >= 0) { + if (inlen >= (ssize_t)sizeof(buffer)) { + fr_strerror_printf("Invalid IPv4 address string \"%s\"", value); + return -1; + } + memcpy(buffer, value, inlen); + buffer[inlen] = '\0'; + value = buffer; + } + + p = strchr(value, '/'); + + /* + * 192.0.2.2 is parsed as if it was /32 + */ + if (!p) { + out->prefix = 32; + out->af = AF_INET; + + /* + * Allow '*' as the wildcard address usually 0.0.0.0 + */ + if ((value[0] == '*') && (value[1] == '\0')) { + out->ipaddr.ip4addr.s_addr = htonl(INADDR_ANY); + + /* + * Convert things which are obviously integers to IP addresses + * + * We assume the number is the bigendian representation of the + * IP address. + */ + } else if (is_integer(value) || ((value[0] == '0') && (value[1] == 'x'))) { + out->ipaddr.ip4addr.s_addr = htonl(strtoul(value, NULL, 0)); + + } else if (!resolve) { + if (inet_pton(AF_INET, value, &out->ipaddr.ip4addr.s_addr) <= 0) { + fr_strerror_printf("Failed to parse IPv4 addreess string \"%s\"", value); + return -1; + } + } else if (ip_hton(out, AF_INET, value, fallback) < 0) return -1; + + return 0; + } + + /* + * Copy the IP portion into a temporary buffer if we haven't already. + */ + if (inlen < 0) memcpy(buffer, value, p - value); + buffer[p - value] = '\0'; + + if (ip_prefix_from_str(buffer, &out->ipaddr.ip4addr.s_addr) <= 0) { + fr_strerror_printf("Failed to parse IPv4 address string \"%s\"", value); + return -1; + } + + mask = strtoul(p + 1, &eptr, 10); + if (mask > 32) { + fr_strerror_printf("Invalid IPv4 mask length \"%s\". Should be between 0-32", p); + return -1; + } + + if (eptr[0] != '\0') { + fr_strerror_printf("Failed to parse IPv4 address string \"%s\", " + "got garbage after mask length \"%s\"", value, eptr); + return -1; + } + + if (mask < 32) { + out->ipaddr.ip4addr = fr_inaddr_mask(&out->ipaddr.ip4addr, mask); + } + + out->prefix = (uint8_t) mask; + out->af = AF_INET; + + return 0; +} + +/** + * Parse an IPv6 address or IPv6 prefix in presentation format (and others), + * or a hostname. + * + * @param out Where to write the ip address value. + * @param value to parse. + * @param inlen Length of value, if value is \0 terminated inlen may be -1. + * @param resolve If true and value doesn't look like an IP address, try and resolve value as a hostname. + * @param fallback to IPv4 resolution if no AAAA records can be found. + * @return 0 if ip address was parsed successfully, else -1 on error. + */ +int fr_pton6(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, bool fallback) +{ + char const *p; + unsigned int prefix; + char *eptr; + + /* IPv6 + / + [0-9]{1,3} or a hostname (RFC1035 2.3.4 Size limits) */ + char buffer[256]; + + /* + * Copy to intermediary buffer if we were given a length + */ + if (inlen >= 0) { + if (inlen >= (ssize_t)sizeof(buffer)) { + fr_strerror_printf("Invalid IPv6 address string \"%s\"", value); + return -1; + } + memcpy(buffer, value, inlen); + buffer[inlen] = '\0'; + value = buffer; + } + + p = strchr(value, '/'); + if (!p) { + out->prefix = 128; + out->af = AF_INET6; + + /* + * Allow '*' as the wildcard address + */ + if ((value[0] == '*') && (value[1] == '\0')) { + memset(out->ipaddr.ip6addr.s6_addr, 0, sizeof(out->ipaddr.ip6addr.s6_addr)); + } else if (!resolve) { + if (inet_pton(AF_INET6, value, out->ipaddr.ip6addr.s6_addr) <= 0) { + fr_strerror_printf("Failed to parse IPv6 address string \"%s\"", value); + return -1; + } + } else if (ip_hton(out, AF_INET6, value, fallback) < 0) return -1; + + return 0; + } + + if ((p - value) >= INET6_ADDRSTRLEN) { + fr_strerror_printf("Invalid IPv6 address string \"%s\"", value); + return -1; + } + + /* + * Copy string to temporary buffer if we didn't do it earlier + */ + if (inlen < 0) memcpy(buffer, value, p - value); + buffer[p - value] = '\0'; + + if (!resolve) { + if (inet_pton(AF_INET6, buffer, out->ipaddr.ip6addr.s6_addr) <= 0) { + fr_strerror_printf("Failed to parse IPv6 address string \"%s\"", value); + return -1; + } + } else if (ip_hton(out, AF_INET6, buffer, fallback) < 0) return -1; + + prefix = strtoul(p + 1, &eptr, 10); + if (prefix > 128) { + fr_strerror_printf("Invalid IPv6 mask length \"%s\". Should be between 0-128", p); + return -1; + } + if (eptr[0] != '\0') { + fr_strerror_printf("Failed to parse IPv6 address string \"%s\", " + "got garbage after mask length \"%s\"", value, eptr); + return -1; + } + + if (prefix < 128) { + struct in6_addr addr; + + addr = fr_in6addr_mask(&out->ipaddr.ip6addr, prefix); + memcpy(out->ipaddr.ip6addr.s6_addr, addr.s6_addr, sizeof(out->ipaddr.ip6addr.s6_addr)); + } + + out->prefix = (uint8_t) prefix; + out->af = AF_INET6; + + return 0; +} + +/** Simple wrapper to decide whether an IP value is v4 or v6 and call the appropriate parser. + * + * @param[out] out Where to write the ip address value. + * @param[in] value to parse. + * @param[in] inlen Length of value, if value is \0 terminated inlen may be -1. + * @param[in] resolve If true and value doesn't look like an IP address, try and resolve value as a + * hostname. + * @param[in] af If the address type is not obvious from the format, and resolve is true, the DNS + * record (A or AAAA) we require. Also controls which parser we pass the address to if + * we have no idea what it is. + * @return + * - 0 if ip address was parsed successfully. + * - -1 on failure. + */ +int fr_pton(fr_ipaddr_t *out, char const *value, ssize_t inlen, int af, bool resolve) +{ + size_t len, i; + bool hostname = true; + bool ipv4 = true; + bool ipv6 = true; + + len = (inlen >= 0) ? (size_t)inlen : strlen(value); + + for (i = 0; i < len; i++) { + /* + * These are valid for IPv4, IPv6, and host names. + */ + if ((value[i] >= '0') && (value[i] <= '9')) { + continue; + } + + /* + * These are invalid for IPv4, but OK for IPv6 + * and host names. + */ + if ((value[i] >= 'a') && (value[i] <= 'f')) { + ipv4 = false; + continue; + } + + /* + * These are invalid for IPv4, but OK for IPv6 + * and host names. + */ + if ((value[i] >= 'A') && (value[i] <= 'F')) { + ipv4 = false; + continue; + } + + /* + * This is only valid for IPv6 addresses. + */ + if (value[i] == ':') { + ipv4 = false; + hostname = false; + continue; + } + + /* + * Valid for IPv4 and host names, not for IPv6. + */ + if (value[i] == '.') { + ipv6 = false; + continue; + } + + /* + * Netmasks are allowed by us, and MUST come at + * the end of the address. + */ + if (value[i] == '/') { + break; + } + + /* + * Any characters other than what are checked for + * above can't be IPv4 or IPv6 addresses. + */ + ipv4 = false; + ipv6 = false; + } + + /* + * It's not an IPv4 or IPv6 address. It MUST be a host + * name. + */ + if (!ipv4 && !ipv6) { + /* + * Not an IPv4 or IPv6 address, and we weren't + * asked to do DNS resolution, we can't do it. + */ + if (!resolve) { + fr_strerror_printf("Not IPv4/6 address, and asked not to resolve"); + return -1; + } + + /* + * It's not a hostname, either, so bail out + * early. + */ + if (!hostname) { + fr_strerror_printf("Invalid address"); + return -1; + } + } + + /* + * The name has a ':' in it. Therefore it must be an + * IPv6 address. Error out if the caller specified IPv4. + * Otherwise, force IPv6. + */ + if (ipv6 && !hostname) { + if (af == AF_INET) { + fr_strerror_printf("Invalid address"); + return -1; + } + + af = AF_INET6; + } + + /* + * Use whatever the caller specified, OR what we + * insinuated above from looking at the name string. + */ + switch (af) { + case AF_UNSPEC: + return fr_pton4(out, value, inlen, resolve, true); + + case AF_INET: + return fr_pton4(out, value, inlen, resolve, false); + + case AF_INET6: + return fr_pton6(out, value, inlen, resolve, false); + + default: + break; + } + + /* + * No idea what it is... + */ + fr_strerror_printf("Invalid address family %i", af); + return -1; +} + +/** Parses IPv4/6 address + port, to fr_ipaddr_t and integer + * + * @param[out] out Where to write the ip address value. + * @param[out] port_out Where to write the port (0 if no port found). + * @param[in] value to parse. + * @param[in] inlen Length of value, if value is \0 terminated inlen may be -1. + * @param[in] af If the address type is not obvious from the format, and resolve is true, the DNS + * record (A or AAAA) we require. Also controls which parser we pass the address to if + * we have no idea what it is. + * @param[in] resolve If true and value doesn't look like an IP address, try and resolve value as a + * hostname. + */ +int fr_pton_port(fr_ipaddr_t *out, uint16_t *port_out, char const *value, ssize_t inlen, int af, bool resolve) +{ + char const *p = value, *q; + char *end; + unsigned long port; + char buffer[6]; + size_t len; + + *port_out = 0; + + len = (inlen >= 0) ? (size_t)inlen : strlen(value); + + if (*p == '[') { + if (!(q = memchr(p + 1, ']', len - 1))) { + fr_strerror_printf("Missing closing ']' for IPv6 address"); + return -1; + } + + /* + * inet_pton doesn't like the address being wrapped in [] + */ + if (fr_pton6(out, p + 1, (q - p) - 1, false, false) < 0) return -1; + + if (q[1] == ':') { + q++; + goto do_port; + } + + return 0; + } + + /* + * Host, IPv4 or IPv6 with no port + */ + q = memchr(p, ':', len); + if (!q) return fr_pton(out, p, len, af, resolve); + + /* + * IPv4 or host, with port + */ + if (fr_pton(out, p, (q - p), af, resolve) < 0) return -1; + +do_port: + /* + * Valid ports are a maximum of 5 digits, so if the + * input length indicates there are more than 5 chars + * after the ':' then there's an issue. + */ + if (len > (size_t) ((q + sizeof(buffer)) - value)) { + error: + fr_strerror_printf("IP string contains trailing garbage after port delimiter"); + return -1; + } + + p = q + 1; /* Move to first digit */ + + strlcpy(buffer, p, (len - (p - value)) + 1); + port = strtoul(buffer, &end, 10); + if (*end != '\0') goto error; /* Trailing garbage after integer */ + + if ((port > UINT16_MAX) || (port == 0)) { + fr_strerror_printf("Port %lu outside valid port range 1-" STRINGIFY(UINT16_MAX), port); + return -1; + } + *port_out = port; + + return 0; +} + +int fr_ntop(char *out, size_t outlen, fr_ipaddr_t const *addr) +{ + char buffer[INET6_ADDRSTRLEN]; + + if (inet_ntop(addr->af, &(addr->ipaddr), buffer, sizeof(buffer)) == NULL) return -1; + + return snprintf(out, outlen, "%s/%i", buffer, addr->prefix); +} + +/* + * cppcheck apparently can't pick this up from the system headers. + */ +#ifdef CPPCHECK +#define F_WRLCK +#endif + +/* + * Internal wrapper for locking, to minimize the number of ifdef's + * + * Use fcntl or error + */ +int rad_lockfd(int fd, int lock_len) +{ +#ifdef F_WRLCK + struct flock fl; + + fl.l_start = 0; + fl.l_len = lock_len; + fl.l_pid = getpid(); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_CUR; + + return fcntl(fd, F_SETLKW, (void *)&fl); +#else +#error "missing definition for F_WRLCK, all file locks will fail" + + return -1; +#endif +} + +/* + * Internal wrapper for locking, to minimize the number of ifdef's + * + * Lock an fd, prefer lockf() over flock() + * Nonblocking version. + */ +int rad_lockfd_nonblock(int fd, int lock_len) +{ +#ifdef F_WRLCK + struct flock fl; + + fl.l_start = 0; + fl.l_len = lock_len; + fl.l_pid = getpid(); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_CUR; + + return fcntl(fd, F_SETLK, (void *)&fl); +#else +#error "missing definition for F_WRLCK, all file locks will fail" + + return -1; +#endif +} + +/* + * Internal wrapper for unlocking, to minimize the number of ifdef's + * in the source. + * + * Unlock an fd, prefer lockf() over flock() + */ +int rad_unlockfd(int fd, int lock_len) +{ +#ifdef F_WRLCK + struct flock fl; + + fl.l_start = 0; + fl.l_len = lock_len; + fl.l_pid = getpid(); + fl.l_type = F_UNLCK; + fl.l_whence = SEEK_CUR; + + return fcntl(fd, F_SETLK, (void *)&fl); +#else +#error "missing definition for F_WRLCK, all file locks will fail" + + return -1; +#endif +} + +/* + * Return an interface-id in standard colon notation + */ +char *ifid_ntoa(char *buffer, size_t size, uint8_t const *ifid) +{ + snprintf(buffer, size, "%x:%x:%x:%x", + (ifid[0] << 8) + ifid[1], (ifid[2] << 8) + ifid[3], + (ifid[4] << 8) + ifid[5], (ifid[6] << 8) + ifid[7]); + return buffer; +} + + +/* + * Return an interface-id from + * one supplied in standard colon notation. + */ +uint8_t *ifid_aton(char const *ifid_str, uint8_t *ifid) +{ + static char const xdigits[] = "0123456789abcdef"; + char const *p, *pch; + int num_id = 0, val = 0, idx = 0; + + for (p = ifid_str; ; ++p) { + if (*p == ':' || *p == '\0') { + if (num_id <= 0) + return NULL; + + /* + * Drop 'val' into the array. + */ + ifid[idx] = (val >> 8) & 0xff; + ifid[idx + 1] = val & 0xff; + if (*p == '\0') { + /* + * Must have all entries before + * end of the string. + */ + if (idx != 6) + return NULL; + break; + } + val = 0; + num_id = 0; + if ((idx += 2) > 6) + return NULL; + } else if ((pch = strchr(xdigits, tolower((uint8_t) *p))) != NULL) { + if (++num_id > 4) + return NULL; + /* + * Dumb version of 'scanf' + */ + val <<= 4; + val |= (pch - xdigits); + } else + return NULL; + } + return ifid; +} + + +#ifndef HAVE_INET_PTON +static int inet_pton4(char const *src, struct in_addr *dst) +{ + int octet; + unsigned int num; + char const *p, *off; + uint8_t tmp[4]; + static char const digits[] = "0123456789"; + + octet = 0; + p = src; + while (1) { + num = 0; + while (*p && ((off = strchr(digits, *p)) != NULL)) { + num *= 10; + num += (off - digits); + + if (num > 255) return 0; + + p++; + } + if (!*p) break; + + /* + * Not a digit, MUST be a dot, else we + * die. + */ + if (*p != '.') { + return 0; + } + + tmp[octet++] = num; + p++; + } + + /* + * End of the string. At the fourth + * octet is OK, anything else is an + * error. + */ + if (octet != 3) { + return 0; + } + tmp[3] = num; + + memcpy(dst, &tmp, sizeof(tmp)); + return 1; +} + + +#ifdef HAVE_STRUCT_SOCKADDR_IN6 +/** Convert presentation level address to network order binary form + * + * @note Does not touch dst unless it's returning 1. + * @note :: in a full address is silently ignored. + * @note Inspired by Mark Andrews. + * @author Paul Vixie, 1996. + * + * @param src presentation level address. + * @param dst where to write output address. + * @return 1 if `src' is a valid [RFC1884 2.2] address, else 0. + */ +static int inet_pton6(char const *src, unsigned char *dst) +{ + static char const xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + u_char tmp[IN6ADDRSZ], *tp, *endp, *colonp; + char const *xdigits, *curtok; + int ch, saw_xdigit; + u_int val; + + memset((tp = tmp), 0, IN6ADDRSZ); + endp = tp + IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + char const *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } + if (tp + INT16SZ > endp) + return (0); + *tp++ = (u_char) (val >> 8) & 0xff; + *tp++ = (u_char) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + INADDRSZ) <= endp) && + inet_pton4(curtok, (struct in_addr *) tp) > 0) { + tp += INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + INT16SZ > endp) + return (0); + *tp++ = (u_char) (val >> 8) & 0xff; + *tp++ = (u_char) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + int const n = tp - colonp; + int i; + + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return (0); + /* bcopy(tmp, dst, IN6ADDRSZ); */ + memcpy(dst, tmp, IN6ADDRSZ); + return (1); +} +#endif + +/* + * Utility function, so that the rest of the server doesn't + * have ifdef's around IPv6 support + */ +int inet_pton(int af, char const *src, void *dst) +{ + if (af == AF_INET) { + return inet_pton4(src, dst); + } +#ifdef HAVE_STRUCT_SOCKADDR_IN6 + + if (af == AF_INET6) { + return inet_pton6(src, dst); + } +#endif + + return -1; +} +#endif + +#ifndef HAVE_INET_NTOP +/* + * Utility function, so that the rest of the server doesn't + * have ifdef's around IPv6 support + */ +char const *inet_ntop(int af, void const *src, char *dst, size_t cnt) +{ + if (af == AF_INET) { + uint8_t const *ipaddr = src; + + if (cnt <= INET_ADDRSTRLEN) return NULL; + + snprintf(dst, cnt, "%d.%d.%d.%d", + ipaddr[0], ipaddr[1], + ipaddr[2], ipaddr[3]); + return dst; + } + + /* + * If the system doesn't define this, we define it + * in missing.h + */ + if (af == AF_INET6) { + struct in6_addr const *ipaddr = src; + + if (cnt <= INET6_ADDRSTRLEN) return NULL; + + snprintf(dst, cnt, "%x:%x:%x:%x:%x:%x:%x:%x", + (ipaddr->s6_addr[0] << 8) | ipaddr->s6_addr[1], + (ipaddr->s6_addr[2] << 8) | ipaddr->s6_addr[3], + (ipaddr->s6_addr[4] << 8) | ipaddr->s6_addr[5], + (ipaddr->s6_addr[6] << 8) | ipaddr->s6_addr[7], + (ipaddr->s6_addr[8] << 8) | ipaddr->s6_addr[9], + (ipaddr->s6_addr[10] << 8) | ipaddr->s6_addr[11], + (ipaddr->s6_addr[12] << 8) | ipaddr->s6_addr[13], + (ipaddr->s6_addr[14] << 8) | ipaddr->s6_addr[15]); + return dst; + } + + return NULL; /* don't support IPv6 */ +} +#endif + +/** Wrappers for IPv4/IPv6 host to IP address lookup + * + * This function returns only one IP address, of the specified address family, + * or the first address (of whatever family), if AF_UNSPEC is used. + * + * If fallback is specified and af is AF_INET, but no AF_INET records were + * found and a record for AF_INET6 exists that record will be returned. + * + * If fallback is specified and af is AF_INET6, and a record with AF_INET4 exists + * that record will be returned instead. + * + * @param out Where to write result. + * @param af To search for in preference. + * @param hostname to search for. + * @param fallback to the other adress family, if no records matching af, found. + * @return 0 on success, else -1 on failure. + */ +int ip_hton(fr_ipaddr_t *out, int af, char const *hostname, bool fallback) +{ + int rcode; + struct addrinfo hints, *ai = NULL, *alt = NULL, *res = NULL; + + /* + * Avoid malloc for IP addresses. This helps us debug + * memory errors when using talloc. + */ +#ifdef TALLOC_DEBUG + if (true) { +#else + if (!fr_hostname_lookups) { +#endif +#ifdef HAVE_STRUCT_SOCKADDR_IN6 + if (af == AF_UNSPEC) { + char const *p; + + for (p = hostname; *p != '\0'; p++) { + if ((*p == ':') || + (*p == '[') || + (*p == ']')) { + af = AF_INET6; + break; + } + } + } +#endif + + if (af == AF_UNSPEC) af = AF_INET; + + if (!inet_pton(af, hostname, &(out->ipaddr))) return -1; + + out->af = af; + return 0; + } + + memset(&hints, 0, sizeof(hints)); + + /* + * If we're falling back we need both IPv4 and IPv6 records + */ + if (fallback) { + hints.ai_family = AF_UNSPEC; + } else { + hints.ai_family = af; + } + + if ((rcode = getaddrinfo(hostname, NULL, &hints, &res)) != 0) { + switch (af) { + default: + case AF_UNSPEC: + fr_strerror_printf("Failed resolving \"%s\" to IP address: %s", + hostname, gai_strerror(rcode)); + return -1; + + case AF_INET: + fr_strerror_printf("Failed resolving \"%s\" to IPv4 address: %s", + hostname, gai_strerror(rcode)); + return -1; + + case AF_INET6: + fr_strerror_printf("Failed resolving \"%s\" to IPv6 address: %s", + hostname, gai_strerror(rcode)); + return -1; + } + } + + for (ai = res; ai; ai = ai->ai_next) { + if ((af == ai->ai_family) || (af == AF_UNSPEC)) break; + if (!alt && fallback && ((ai->ai_family == AF_INET) || (ai->ai_family == AF_INET6))) alt = ai; + } + + if (!ai) ai = alt; + if (!ai) { + fr_strerror_printf("ip_hton failed to find requested information for host %.100s", hostname); + freeaddrinfo(res); + return -1; + } + + rcode = fr_sockaddr2ipaddr((struct sockaddr_storage *)ai->ai_addr, + ai->ai_addrlen, out, NULL); + freeaddrinfo(res); + if (!rcode) { + fr_strerror_printf("Failed converting sockaddr to ipaddr"); + return -1; + } + + return 0; +} + +/* + * Look IP addresses up, and print names (depending on DNS config) + */ +char const *ip_ntoh(fr_ipaddr_t const *src, char *dst, size_t cnt) +{ + struct sockaddr_storage ss; + int error; + socklen_t salen; + + /* + * No DNS lookups + */ + if (!fr_dns_lookups) { + return inet_ntop(src->af, &(src->ipaddr), dst, cnt); + } + + if (!fr_ipaddr2sockaddr(src, 0, &ss, &salen)) { + return NULL; + } + + if ((error = getnameinfo((struct sockaddr *)&ss, salen, dst, cnt, NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { + fr_strerror_printf("ip_ntoh: %s", gai_strerror(error)); + return NULL; + } + return dst; +} + +/** Mask off a portion of an IPv4 address + * + * @param ipaddr to mask. + * @param prefix Number of contiguous bits to mask. + * @return an ipv4 address with the host portion zeroed out. + */ +struct in_addr fr_inaddr_mask(struct in_addr const *ipaddr, uint8_t prefix) +{ + uint32_t ret; + + if (prefix > 32) prefix = 32; + + /* Short circuit */ + if (prefix == 32) return *ipaddr; + + if (prefix == 0) ret = 0; + else ret = htonl(~((0x00000001UL << (32 - prefix)) - 1)) & ipaddr->s_addr; + + return (*(struct in_addr *)&ret); +} + +/** Mask off a portion of an IPv6 address + * + * @param ipaddr to mask. + * @param prefix Number of contiguous bits to mask. + * @return an ipv6 address with the host portion zeroed out. + */ +struct in6_addr fr_in6addr_mask(struct in6_addr const *ipaddr, uint8_t prefix) +{ + uint64_t const *p = (uint64_t const *) ipaddr; + uint64_t addr; /* Needed for alignment */ + uint64_t ret[2], *o = ret; + + if (prefix > 128) prefix = 128; + + /* Short circuit */ + if (prefix == 128) return *ipaddr; + + if (prefix >= 64) { + prefix -= 64; + memcpy(&addr, p, sizeof(addr)); /* Needed for aligned access (ubsan) */ + *o++ = 0xffffffffffffffffULL & addr; /* lhs portion masked */ + p++; + } else { + ret[1] = 0; /* rhs portion zeroed */ + } + + /* Max left shift is 63 else we get overflow */ + if (prefix > 0) { + memcpy(&addr, p, sizeof(addr)); /* Needed for aligned access (ubsan) */ + *o = htonll(~((uint64_t)(0x0000000000000001ULL << (64 - prefix)) - 1)) & addr; + } else { + *o = 0; + } + + return *(struct in6_addr *) &ret; +} + +/** Zeroes out the host portion of an fr_ipaddr_t + * + * @param[in,out] addr to mask + * @param[in] prefix Length of the network portion. + */ +void fr_ipaddr_mask(fr_ipaddr_t *addr, uint8_t prefix) +{ + + switch (addr->af) { + case AF_INET: + addr->ipaddr.ip4addr = fr_inaddr_mask(&addr->ipaddr.ip4addr, prefix); + break; + + case AF_INET6: + addr->ipaddr.ip6addr = fr_in6addr_mask(&addr->ipaddr.ip6addr, prefix); + break; + + default: + return; + } + addr->prefix = prefix; +} + +static char const hextab[] = "0123456789abcdef"; + +/** Convert hex strings to binary data + * + * @param bin Buffer to write output to. + * @param outlen length of output buffer (or length of input string / 2). + * @param hex input string. + * @param inlen length of the input string + * @return length of data written to buffer. + */ +size_t fr_hex2bin(uint8_t *bin, size_t outlen, char const *hex, size_t inlen) +{ + size_t i; + size_t len; + char *c1, *c2; + + /* + * Smartly truncate output, caller should check number of bytes + * written. + */ + len = inlen >> 1; + if (len > outlen) len = outlen; + + for (i = 0; i < len; i++) { + if(!(c1 = memchr(hextab, tolower((uint8_t) hex[i << 1]), sizeof(hextab))) || + !(c2 = memchr(hextab, tolower((uint8_t) hex[(i << 1) + 1]), sizeof(hextab)))) + break; + bin[i] = ((c1-hextab)<<4) + (c2-hextab); + } + + return i; +} + +/** Convert binary data to a hex string + * + * Ascii encoded hex string will not be prefixed with '0x' + * + * @warning If the output buffer isn't long enough, we have a buffer overflow. + * + * @param[out] hex Buffer to write hex output. + * @param[in] bin input. + * @param[in] inlen of bin input. + * @return length of data written to buffer. + */ +size_t fr_bin2hex(char *hex, uint8_t const *bin, size_t inlen) +{ + size_t i; + + for (i = 0; i < inlen; i++) { + hex[0] = hextab[((*bin) >> 4) & 0x0f]; + hex[1] = hextab[*bin & 0x0f]; + hex += 2; + bin++; + } + + *hex = '\0'; + return inlen * 2; +} + +/** Convert binary data to a hex string + * + * Ascii encoded hex string will not be prefixed with '0x' + * + * @param[in] ctx to alloc buffer in. + * @param[in] bin input. + * @param[in] inlen of bin input. + * @return length of data written to buffer. + */ +char *fr_abin2hex(TALLOC_CTX *ctx, uint8_t const *bin, size_t inlen) +{ + char *buff; + + buff = talloc_array(ctx, char, (inlen << 2)); + if (!buff) return NULL; + + fr_bin2hex(buff, bin, inlen); + + return buff; +} + +/** Consume the integer (or hex) portion of a value string + * + * @param value string to parse. + * @param end pointer to the first non numeric char. + * @return integer value. + */ +uint32_t fr_strtoul(char const *value, char **end) +{ + if ((value[0] == '0') && (value[1] == 'x')) { + return strtoul(value, end, 16); + } + + return strtoul(value, end, 10); +} + +/** Check whether the string is all whitespace + * + * @return true if the entirety of the string is whitespace, else false. + */ +bool is_whitespace(char const *value) +{ + do { + if (!isspace((uint8_t) *value)) return false; + value++; + } while (*value); + + return true; +} + +/** Check whether the string is made up of printable UTF8 chars + * + * @param value to check. + * @param len of value. + * + * @return + * - true if the string is printable. + * - false if the string contains non printable chars + */ + bool is_printable(void const *value, size_t len) + { + uint8_t const *p = value; + int clen; + size_t i; + + for (i = 0; i < len; i++) { + clen = fr_utf8_char(p, len - i); + if (clen == 0) return false; + i += (size_t)clen; + p += clen; + } + return true; + } + +/** Check whether the string is all numbers + * + * @return true if the entirety of the string is all numbers, else false. + */ +bool is_integer(char const *value) +{ +#ifndef __clang_analyzer__ + do { + if (!isdigit((uint8_t) *value)) return false; + value++; + } while (*value); + + /* + * Clang analyzer complains about the above line: "Branch + * depends on a garbage value", even though that's + * clearly not true. And, it doesn't complain about the + * other functions doing similar things. + */ +#else + if (!isdigit((uint8_t) *value)) return false; +#endif + + return true; +} + +/** Check whether the string is allzeros + * + * @return true if the entirety of the string is all zeros, else false. + */ +bool is_zero(char const *value) +{ + do { + if (*value != '0') return false; + value++; + } while (*value); + + return true; +} + +/* + * So we don't have ifdef's in the rest of the code + */ +#ifndef HAVE_CLOSEFROM +int closefrom(int fd) +{ + int i; + int maxfd = 256; +#ifdef HAVE_DIRENT_H + DIR *dir; +#endif + +#ifdef F_CLOSEM + if (fcntl(fd, F_CLOSEM) == 0) { + return 0; + } +#endif + +#ifdef F_MAXFD + maxfd = fcntl(fd, F_F_MAXFD); + if (maxfd >= 0) goto do_close; +#endif + +#ifdef _SC_OPEN_MAX + maxfd = sysconf(_SC_OPEN_MAX); + if (maxfd < 0) { + maxfd = 256; + } +#endif + +#ifdef HAVE_DIRENT_H + /* + * Use /proc/self/fd directory if it exists. + */ + dir = opendir(CLOSEFROM_DIR); + if (dir != NULL) { + long my_fd; + char *endp; + struct dirent *dp; + + while ((dp = readdir(dir)) != NULL) { + my_fd = strtol(dp->d_name, &endp, 10); + if (my_fd <= 0) continue; + + if (*endp) continue; + + if (my_fd == dirfd(dir)) continue; + + if ((my_fd >= fd) && (my_fd <= maxfd)) { + (void) close((int) my_fd); + } + } + (void) closedir(dir); + return 0; + } +#endif + +#ifdef F_MAXFD +do_close: +#endif + + if (fd > maxfd) return 0; + + /* + * FIXME: return EINTR? + */ + for (i = fd; i < maxfd; i++) { + close(i); + } + + return 0; +} +#endif + +int fr_ipaddr_cmp(fr_ipaddr_t const *a, fr_ipaddr_t const *b) +{ + if (a->af < b->af) return -1; + if (a->af > b->af) return +1; + + if (a->prefix < b->prefix) return -1; + if (a->prefix > b->prefix) return +1; + + switch (a->af) { + case AF_INET: + return memcmp(&a->ipaddr.ip4addr, + &b->ipaddr.ip4addr, + sizeof(a->ipaddr.ip4addr)); + +#ifdef HAVE_STRUCT_SOCKADDR_IN6 + case AF_INET6: + if (a->scope < b->scope) return -1; + if (a->scope > b->scope) return +1; + + return memcmp(&a->ipaddr.ip6addr, + &b->ipaddr.ip6addr, + sizeof(a->ipaddr.ip6addr)); +#endif + + default: + break; + } + + return -1; +} + +int fr_ipaddr2sockaddr(fr_ipaddr_t const *ipaddr, uint16_t port, + struct sockaddr_storage *sa, socklen_t *salen) +{ + memset(sa, 0, sizeof(*sa)); + + if (ipaddr->af == AF_INET) { + struct sockaddr_in s4; + + *salen = sizeof(s4); + + memset(&s4, 0, sizeof(s4)); + s4.sin_family = AF_INET; + s4.sin_addr = ipaddr->ipaddr.ip4addr; + s4.sin_port = htons(port); + memset(sa, 0, sizeof(*sa)); + memcpy(sa, &s4, sizeof(s4)); + +#ifdef HAVE_STRUCT_SOCKADDR_IN6 + } else if (ipaddr->af == AF_INET6) { + struct sockaddr_in6 s6; + + *salen = sizeof(s6); + + memset(&s6, 0, sizeof(s6)); + s6.sin6_family = AF_INET6; + s6.sin6_addr = ipaddr->ipaddr.ip6addr; + s6.sin6_port = htons(port); + s6.sin6_scope_id = ipaddr->scope; + memset(sa, 0, sizeof(*sa)); + memcpy(sa, &s6, sizeof(s6)); +#endif + } else { + return 0; + } + + return 1; +} + + +int fr_sockaddr2ipaddr(struct sockaddr_storage const *sa, socklen_t salen, + fr_ipaddr_t *ipaddr, uint16_t *port) +{ + memset(ipaddr, 0, sizeof(*ipaddr)); + + if (sa->ss_family == AF_INET) { + struct sockaddr_in s4; + + if (salen < sizeof(s4)) { + fr_strerror_printf("IPv4 address is too small"); + return 0; + } + + memcpy(&s4, sa, sizeof(s4)); + ipaddr->af = AF_INET; + ipaddr->prefix = 32; + ipaddr->ipaddr.ip4addr = s4.sin_addr; + if (port) *port = ntohs(s4.sin_port); + +#ifdef HAVE_STRUCT_SOCKADDR_IN6 + } else if (sa->ss_family == AF_INET6) { + struct sockaddr_in6 s6; + + if (salen < sizeof(s6)) { + fr_strerror_printf("IPv6 address is too small"); + return 0; + } + + memcpy(&s6, sa, sizeof(s6)); + ipaddr->af = AF_INET6; + ipaddr->prefix = 128; + ipaddr->ipaddr.ip6addr = s6.sin6_addr; + if (port) *port = ntohs(s6.sin6_port); + ipaddr->scope = s6.sin6_scope_id; +#endif + + } else { + fr_strerror_printf("Unsupported address famility %d", + sa->ss_family); + return 0; + } + + return 1; +} + +#ifdef O_NONBLOCK +/** Set O_NONBLOCK on a socket + * + * @note O_NONBLOCK is POSIX. + * + * @param fd to set nonblocking flag on. + * @return flags set on the socket, or -1 on error. + */ +int fr_nonblock(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL, NULL); + if (flags < 0) { + fr_strerror_printf("Failure getting socket flags: %s", fr_syserror(errno)); + return -1; + } + + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) { + fr_strerror_printf("Failure setting socket flags: %s", fr_syserror(errno)); + return -1; + } + + return flags; +} + +/** Unset O_NONBLOCK on a socket + * + * @note O_NONBLOCK is POSIX. + * + * @param fd to set nonblocking flag on. + * @return flags set on the socket, or -1 on error. + */ +int fr_blocking(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL, NULL); + if (flags < 0) { + fr_strerror_printf("Failure getting socket flags: %s", fr_syserror(errno)); + return -1; + } + + flags ^= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) { + fr_strerror_printf("Failure setting socket flags: %s", fr_syserror(errno)); + return -1; + } + + return flags; +} +#else +int fr_nonblock(UNUSED int fd) +{ + fr_strerror_printf("Non blocking sockets are not supported"); + return -1; +} +int fr_blocking(UNUSED int fd) +{ + fr_strerror_printf("Non blocking sockets are not supported"); + return -1; +} +#endif + +/** Write out a vector to a file descriptor + * + * Wraps writev, calling it as necessary. If timeout is not NULL, + * timeout is applied to each call that returns EAGAIN or EWOULDBLOCK + * + * @note Should only be used on nonblocking file descriptors. + * @note Socket should likely be closed on timeout. + * @note iovec may be modified in such a way that it's not re-usable. + * @note Leaves errno set to the last error that ocurred. + * + * @param fd to write to. + * @param vector to write. + * @param iovcnt number of elements in iovec. + * @param timeout how long to wait for fd to become writeable before timing out. + * @return number of bytes written, -1 on error. + */ +ssize_t fr_writev(int fd, struct iovec vector[], int iovcnt, struct timeval *timeout) +{ + struct iovec *vector_p = vector; + ssize_t total = 0; + + while (iovcnt > 0) { + ssize_t wrote; + + wrote = writev(fd, vector_p, iovcnt); + if (wrote > 0) { + total += wrote; + while (wrote > 0) { + /* + * An entire vector element was written + */ + if (wrote >= (ssize_t)vector_p->iov_len) { + iovcnt--; + wrote -= vector_p->iov_len; + vector_p++; + continue; + } + + /* + * Partial vector element was written + */ + vector_p->iov_len -= wrote; + vector_p->iov_base = ((char *)vector_p->iov_base) + wrote; + break; + } + continue; + } else if (wrote == 0) return total; + + switch (errno) { + /* Write operation would block, use select() to implement a timeout */ +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: + case EAGAIN: +#else + case EAGAIN: +#endif + { + int ret; + fd_set write_set; + + FD_ZERO(&write_set); + FD_SET(fd, &write_set); + + /* Don't let signals mess up the select */ + do { + ret = select(fd + 1, NULL, &write_set, NULL, timeout); + } while ((ret == -1) && (errno == EINTR)); + + /* Select returned 0 which means it reached the timeout */ + if (ret == 0) { + fr_strerror_printf("Write timed out"); + return -1; + } + + /* Other select error */ + if (ret < 0) { + fr_strerror_printf("Failed waiting on socket: %s", fr_syserror(errno)); + return -1; + } + + /* select said a file descriptor was ready for writing */ + if (!fr_assert(FD_ISSET(fd, &write_set))) return -1; + + break; + } + + default: + return -1; + } + } + + return total; +} + +/** Convert UTF8 string to UCS2 encoding + * + * @note Borrowed from src/crypto/ms_funcs.c of wpa_supplicant project (http://hostap.epitest.fi/wpa_supplicant/) + * + * @param[out] out Where to write the ucs2 string. + * @param[in] outlen Size of output buffer. + * @param[in] in UTF8 string to convert. + * @param[in] inlen length of UTF8 string. + * @return the size of the UCS2 string written to the output buffer (in bytes). + */ +ssize_t fr_utf8_to_ucs2(uint8_t *out, size_t outlen, char const *in, size_t inlen) +{ + size_t i; + uint8_t *start = out; + + for (i = 0; i < inlen; i++) { + uint8_t c, c2, c3; + + c = in[i]; + if ((size_t)(out - start) >= outlen) { + /* input too long */ + return -1; + } + + /* One-byte encoding */ + if (c <= 0x7f) { + FR_PUT_LE16(out, c); + out += 2; + continue; + } else if ((i == (inlen - 1)) || ((size_t)(out - start) >= (outlen - 1))) { + /* Incomplete surrogate */ + return -1; + } + + c2 = in[++i]; + /* Two-byte encoding */ + if ((c & 0xe0) == 0xc0) { + FR_PUT_LE16(out, ((c & 0x1f) << 6) | (c2 & 0x3f)); + out += 2; + continue; + } + if ((i == inlen) || ((size_t)(out - start) >= (outlen - 1))) { + /* Incomplete surrogate */ + return -1; + } + + /* Three-byte encoding */ + c3 = in[++i]; + FR_PUT_LE16(out, ((c & 0xf) << 12) | ((c2 & 0x3f) << 6) | (c3 & 0x3f)); + out += 2; + } + + return out - start; +} + +/** Write 128bit unsigned integer to buffer + * + * @author Alexey Frunze + * + * @param out where to write result to. + * @param outlen size of out. + * @param num 128 bit integer. + */ +size_t fr_prints_uint128(char *out, size_t outlen, uint128_t const num) +{ + char buff[128 / 3 + 1 + 1]; + uint64_t n[2]; + char *p = buff; + int i; +#ifdef FR_LITTLE_ENDIAN + const size_t l = 0; + const size_t h = 1; +#else + const size_t l = 1; + const size_t h = 0; +#endif + + memset(buff, '0', sizeof(buff) - 1); + buff[sizeof(buff) - 1] = '\0'; + + memcpy(n, &num, sizeof(n)); + + for (i = 0; i < 128; i++) { + ssize_t j; + int carry; + + carry = (n[h] >= 0x8000000000000000); + + // Shift n[] left, doubling it + n[h] = ((n[h] << 1) & 0xffffffffffffffff) + (n[l] >= 0x8000000000000000); + n[l] = ((n[l] << 1) & 0xffffffffffffffff); + + // Add s[] to itself in decimal, doubling it + for (j = sizeof(buff) - 2; j >= 0; j--) { + buff[j] += buff[j] - '0' + carry; + carry = (buff[j] > '9'); + if (carry) { + buff[j] -= 10; + } + } + } + + while ((*p == '0') && (p < &buff[sizeof(buff) - 2])) { + p++; + } + + return strlcpy(out, p, outlen); +} + +/* + * Sort of strtok/strsep function. + */ +static char *mystrtok(char **ptr, char const *sep) +{ + char *res; + + if (**ptr == 0) { + return NULL; + } + + while (**ptr && strchr(sep, **ptr)) { + (*ptr)++; + } + if (**ptr == 0) { + return NULL; + } + + res = *ptr; + while (**ptr && strchr(sep, **ptr) == NULL) { + (*ptr)++; + } + + if (**ptr != 0) { + *(*ptr)++ = 0; + } + return res; +} + +/** Convert string in various formats to a time_t + * + * @param date_str input date string. + * @param date time_t to write result to. + * @return 0 on success or -1 on error. + */ +int fr_get_time(char const *date_str, time_t *date) +{ + int i, j; + time_t t; + struct tm *tm, s_tm; + char buf[64]; + char *p; + char *f[4]; + char *tail = NULL; + + /* + * Test for unix timestamp date + */ + *date = strtoul(date_str, &tail, 10); + if (*tail == '\0') { + return 0; + } + + tm = &s_tm; + memset(tm, 0, sizeof(*tm)); + tm->tm_isdst = -1; /* don't know, and don't care about DST */ + + strlcpy(buf, date_str, sizeof(buf)); + + p = buf; + f[0] = mystrtok(&p, " \t"); + f[1] = mystrtok(&p, " \t"); + f[2] = mystrtok(&p, " \t"); + f[3] = mystrtok(&p, " \t"); /* may, or may not, be present */ + if (!f[0] || !f[1] || !f[2]) return -1; + + /* + * The time has a colon, where nothing else does. + * So if we find it, bubble it to the back of the list. + */ + if (f[3]) { + for (i = 0; i < 3; i++) { + if (strchr(f[i], ':')) { + p = f[3]; + f[3] = f[i]; + f[i] = p; + break; + } + } + } + + /* + * The month is text, which allows us to find it easily. + */ + tm->tm_mon = 12; + for (i = 0; i < 3; i++) { + if (isalpha( (int) *f[i])) { + /* + * Bubble the month to the front of the list + */ + p = f[0]; + f[0] = f[i]; + f[i] = p; + + for (j = 0; j < 12; j++) { + if (strncasecmp(months[j], f[0], 3) == 0) { + tm->tm_mon = j; + break; + } + } + } + } + + /* month not found? */ + if (tm->tm_mon == 12) return -1; + + /* + * The year may be in f[1], or in f[2] + */ + tm->tm_year = atoi(f[1]); + tm->tm_mday = atoi(f[2]); + + if (tm->tm_year >= 1900) { + tm->tm_year -= 1900; + + } else { + /* + * We can't use 2-digit years any more, they make it + * impossible to tell what's the day, and what's the year. + */ + if (tm->tm_mday < 1900) return -1; + + /* + * Swap the year and the day. + */ + i = tm->tm_year; + tm->tm_year = tm->tm_mday - 1900; + tm->tm_mday = i; + } + + /* + * If the day is out of range, die. + */ + if ((tm->tm_mday < 1) || (tm->tm_mday > 31)) { + return -1; + } + + /* + * There may be %H:%M:%S. Parse it in a hacky way. + */ + if (f[3]) { + f[0] = f[3]; /* HH */ + f[1] = strchr(f[0], ':'); /* find : separator */ + if (!f[1]) return -1; + + *(f[1]++) = '\0'; /* nuke it, and point to MM:SS */ + + f[2] = strchr(f[1], ':'); /* find : separator */ + if (f[2]) { + *(f[2]++) = '\0'; /* nuke it, and point to SS */ + tm->tm_sec = atoi(f[2]); + } /* else leave it as zero */ + + tm->tm_hour = atoi(f[0]); + tm->tm_min = atoi(f[1]); + } + + /* + * Returns -1 on error. + */ + t = mktime(tm); + if (t == (time_t) -1) return -1; + + *date = t; + + return 0; +} + +/** Compares two pointers + * + * @param a first pointer to compare. + * @param b second pointer to compare. + * @return -1 if a < b, +1 if b > a, or 0 if both equal. + */ +int8_t fr_pointer_cmp(void const *a, void const *b) +{ + if (a < b) return -1; + if (a == b) return 0; + + return 1; +} + +static int _quick_partition(void const *to_sort[], int min, int max, fr_cmp_t cmp) { + void const *pivot = to_sort[min]; + int i = min; + int j = max + 1; + void const *tmp; + + for (;;) { + do ++i; while((cmp(to_sort[i], pivot) <= 0) && i <= max); + do --j; while(cmp(to_sort[j], pivot) > 0); + + if (i >= j) break; + + tmp = to_sort[i]; + to_sort[i] = to_sort[j]; + to_sort[j] = tmp; + } + + tmp = to_sort[min]; + to_sort[min] = to_sort[j]; + to_sort[j] = tmp; + + return j; +} + +/** Quick sort an array of pointers using a comparator + * + * @param to_sort array of pointers to sort. + * @param min_idx the lowest index (usually 0). + * @param max_idx the highest index (usually length of array - 1). + * @param cmp the comparison function to use to sort the array elements. + */ +void fr_quick_sort(void const *to_sort[], int min_idx, int max_idx, fr_cmp_t cmp) +{ + int part; + + if (min_idx >= max_idx) return; + + part = _quick_partition(to_sort, min_idx, max_idx, cmp); + fr_quick_sort(to_sort, min_idx, part - 1, cmp); + fr_quick_sort(to_sort, part + 1, max_idx, cmp); +} + +#define USEC 1000000 + +/** Convert a time specified in milliseconds to a timeval + * + * @param[out] out Where to write the result. + * @param[in] ms To convert to a timeval struct. + */ +void fr_timeval_from_ms(struct timeval *out, uint64_t ms) +{ + out->tv_sec = ms / 1000; + out->tv_usec = (ms % 1000) * 1000; +} + +/** Convert a time specified in microseconds to a timeval + * + * @param[out] out Where to write the result. + * @param[in] usec To convert to a timeval struct. + */ +void fr_timeval_from_usec(struct timeval *out, uint64_t usec) +{ + out->tv_sec = usec / USEC; + out->tv_usec = usec % USEC; +} + +#ifdef TALLOC_DEBUG +void fr_talloc_verify_cb(UNUSED const void *ptr, UNUSED int depth, + UNUSED int max_depth, UNUSED int is_ref, + UNUSED void *private_data) +{ + /* do nothing */ +} +#endif diff --git a/src/lib/missing.c b/src/lib/missing.c new file mode 100644 index 0000000..95b8c54 --- /dev/null +++ b/src/lib/missing.c @@ -0,0 +1,443 @@ +/* + * missing.c Replacements for functions that are or can be + * missing on some platforms. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#include <ctype.h> + +#ifndef HAVE_CRYPT +char *crypt(UNUSED char *key, char *salt) +{ + /*log(L_ERR, "crypt() called but not implemented");*/ + return salt; +} +#endif + +#ifndef HAVE_STRNCASECMP +int strncasecmp(char *s1, char *s2, int n) +{ + int dif; + unsigned char *p1, *p2; + int c1, c2; + + p1 = (unsigned char *)s1; + p2 = (unsigned char *)s2; + dif = 0; + + while (n != 0) { + if (*p1 == 0 && *p2 == 0) + break; + c1 = *p1; + c2 = *p2; + + if (islower(c1)) c1 = toupper(c1); + if (islower(c2)) c2 = toupper(c2); + + if ((dif = c1 - c2) != 0) + break; + p1++; + p2++; + n--; + } + return dif; +} +#endif + +#ifndef HAVE_STRCASECMP +int strcasecmp(char *s1, char *s2) +{ + int l1, l2; + + l1 = strlen(s1); + l2 = strlen(s2); + if (l2 > l1) l1 = l2; + + return strncasecmp(s1, s2, l1); +} +#endif + +#ifndef HAVE_INET_ATON +int inet_aton(char const *cp, struct in_addr *inp) +{ + int a1, a2, a3, a4; + + if (sscanf(cp, "%d.%d.%d.%d", &a1, &a2, &a3, &a4) != 4) + return 0; + + inp->s_addr = htonl((a1 << 24) + (a2 << 16) + (a3 << 8) + a4); + return 1; +} +#endif + +#ifndef HAVE_STRSEP +/* + * Get next token from string *stringp, where tokens are + * possibly-empty strings separated by characters from delim. + * + * Writes NULs into the string at *stringp to end tokens. + * delim need not remain constant from call to call. On + * return, *stringp points past the last NUL written (if there + * might be further tokens), or is NULL (if there are + * definitely no more tokens). + * + * If *stringp is NULL, strsep returns NULL. + */ +char * +strsep(char **stringp, char const *delim) +{ + char *s; + char const *spanp; + int c, sc; + char *tok; + + if ((s = *stringp) == NULL) + return (NULL); + + for (tok = s;;) { + c = *s++; + spanp = delim; + do { + if ((sc = *spanp++) == c) { + if (c == 0) + s = NULL; + else + s[-1] = 0; + *stringp = s; + return (tok); + } + } while (sc != 0); + } + + return NULL; /* NOTREACHED, but the compiler complains */ +} +#endif + +#ifndef HAVE_LOCALTIME_R +/* + * We use localtime_r() by default in the server. + * + * For systems which do NOT have localtime_r(), we make the + * assumption that localtime() is re-entrant, and returns a + * per-thread data structure. + * + * Even if localtime is NOT re-entrant, this function will + * lower the possibility of race conditions. + */ +struct tm *localtime_r(time_t const *l_clock, struct tm *result) +{ + memcpy(result, localtime(l_clock), sizeof(*result)); + + return result; +} +#endif + +#ifndef HAVE_CTIME_R +/* + * We use ctime_r() by default in the server. + * + * For systems which do NOT have ctime_r(), we make the + * assumption that ctime() is re-entrant, and returns a + * per-thread data structure. + * + * Even if ctime is NOT re-entrant, this function will + * lower the possibility of race conditions. + */ +char *ctime_r(time_t const *l_clock, char *l_buf) +{ + strcpy(l_buf, ctime(l_clock)); + + return l_buf; +} +#endif + +#ifndef HAVE_GMTIME_R +/* + * We use gmtime_r() by default in the server. + * + * For systems which do NOT have gmtime_r(), we make the + * assumption that gmtime() is re-entrant, and returns a + * per-thread data structure. + * + * Even if gmtime is NOT re-entrant, this function will + * lower the possibility of race conditions. + */ +struct tm *gmtime_r(time_t const *l_clock, struct tm *result) +{ + memcpy(result, gmtime(l_clock), sizeof(*result)); + + return result; +} +#endif + +#ifndef HAVE_VDPRINTF +int vdprintf (int fd, char const *format, va_list args) +{ + int ret; + FILE *fp; + int dup_fd; + + dup_fd = dup(fd); + if (dup_fd < 0) return -1; + + fp = fdopen(fd, "w"); + if (!fp) { + close(dup_fd); + return -1; + } + + ret = vfprintf(fp, format, args); + fclose(fp); /* Also closes dup_fd */ + + return ret; +} +#endif + +#ifndef HAVE_GETTIMEOFDAY +#ifdef WIN32 +/* + * Number of micro-seconds between the beginning of the Windows epoch + * (Jan. 1, 1601) and the Unix epoch (Jan. 1, 1970). + * + * This assumes all Win32 compilers have 64-bit support. + */ +#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) || defined(__WATCOMC__) +#define DELTA_EPOCH_IN_USEC 11644473600000000Ui64 +#else +#define DELTA_EPOCH_IN_USEC 11644473600000000ULL +#endif + +static uint64_t filetime_to_unix_epoch (FILETIME const *ft) +{ + uint64_t res = (uint64_t) ft->dwHighDateTime << 32; + + res |= ft->dwLowDateTime; + res /= 10; /* from 100 nano-sec periods to usec */ + res -= DELTA_EPOCH_IN_USEC; /* from Win epoch to Unix epoch */ + return (res); +} + +int gettimeofday (struct timeval *tv, UNUSED void *tz) +{ + FILETIME ft; + uint64_t tim; + + if (!tv) { + errno = EINVAL; + return (-1); + } + GetSystemTimeAsFileTime (&ft); + tim = filetime_to_unix_epoch (&ft); + tv->tv_sec = (long) (tim / 1000000L); + tv->tv_usec = (long) (tim % 1000000L); + return (0); +} +#endif +#endif + +#define NTP_EPOCH_OFFSET 2208988800ULL + +/* + * Convert 'struct timeval' into NTP format (32-bit integer + * of seconds, 32-bit integer of fractional seconds) + */ +void +timeval2ntp(struct timeval const *tv, uint8_t *ntp) +{ + uint32_t sec, usec; + + sec = tv->tv_sec + NTP_EPOCH_OFFSET; + usec = tv->tv_usec * 4295; /* close enough to 2^32 / USEC */ + usec -= ((tv->tv_usec * 2143) >> 16); /* */ + + sec = htonl(sec); + usec = htonl(usec); + + memcpy(ntp, &sec, sizeof(sec)); + memcpy(ntp + sizeof(sec), &usec, sizeof(usec)); +} + +/* + * Inverse of timeval2ntp + */ +void +ntp2timeval(struct timeval *tv, char const *ntp) +{ + uint32_t sec, usec; + + memcpy(&sec, ntp, sizeof(sec)); + memcpy(&usec, ntp + sizeof(sec), sizeof(usec)); + + sec = ntohl(sec); + usec = ntohl(usec); + + tv->tv_sec = sec - NTP_EPOCH_OFFSET; + tv->tv_usec = usec / 4295; /* close enough */ +} + +#if !defined(HAVE_128BIT_INTEGERS) && defined(FR_LITTLE_ENDIAN) +/** Swap byte order of 128 bit integer + * + * @param num 128bit integer to swap. + * @return 128bit integer reversed. + */ +uint128_t ntohlll(uint128_t const num) +{ + uint64_t const *p = (uint64_t const *) # + uint64_t ret[2]; + + /* swapsies */ + ret[1] = ntohll(p[0]); + ret[0] = ntohll(p[1]); + + return *(uint128_t *)ret; +} +#endif + +#ifdef HAVE_OPENSSL_HMAC_H +# ifndef HAVE_HMAC_CTX_NEW +HMAC_CTX *HMAC_CTX_new(void) +{ + HMAC_CTX *ctx; + ctx = OPENSSL_malloc(sizeof(*ctx)); + if (!ctx) return NULL; + + memset(ctx, 0, sizeof(*ctx)); + HMAC_CTX_init(ctx); + return ctx; +} +# endif +# ifndef HAVE_HMAC_CTX_FREE +void HMAC_CTX_free(HMAC_CTX *ctx) +{ + if (ctx == NULL) { + return; + } + HMAC_CTX_cleanup(ctx); + OPENSSL_free(ctx); +} +# endif +#endif + +#ifdef HAVE_OPENSSL_SSL_H +# ifndef HAVE_SSL_GET_CLIENT_RANDOM +size_t SSL_get_client_random(const SSL *s, unsigned char *out, size_t outlen) +{ + if (!outlen) return sizeof(s->s3->client_random); + + if (outlen > sizeof(s->s3->client_random)) outlen = sizeof(s->s3->client_random); + + memcpy(out, s->s3->client_random, outlen); + return outlen; +} +# endif +# ifndef HAVE_SSL_GET_SERVER_RANDOM +size_t SSL_get_server_random(const SSL *s, unsigned char *out, size_t outlen) +{ + if (!outlen) return sizeof(s->s3->server_random); + + if (outlen > sizeof(s->s3->server_random)) outlen = sizeof(s->s3->server_random); + + memcpy(out, s->s3->server_random, outlen); + return outlen; +} +# endif +# ifndef HAVE_SSL_SESSION_GET_MASTER_KEY +size_t SSL_SESSION_get_master_key(const SSL_SESSION *s, + unsigned char *out, size_t outlen) +{ + if (!outlen) return s->master_key_length; + + if (outlen > (size_t)s->master_key_length) outlen = (size_t)s->master_key_length; + + memcpy(out, s->master_key, outlen); + return outlen; +} +# endif +#endif + +/** Call talloc strdup, setting the type on the new chunk correctly + * + * For some bizarre reason the talloc string functions don't set the + * memory chunk type to char, which causes all kinds of issues with + * verifying VALUE_PAIRs. + * + * @param[in] t The talloc context to hang the result off. + * @param[in] p The string you want to duplicate. + * @return The duplicated string, NULL on error. + */ +char *talloc_typed_strdup(void const *t, char const *p) +{ + char *n; + + n = talloc_strdup(t, p); + if (!n) return NULL; + talloc_set_type(n, char); + + return n; +} + +/** Call talloc vasprintf, setting the type on the new chunk correctly + * + * For some bizarre reason the talloc string functions don't set the + * memory chunk type to char, which causes all kinds of issues with + * verifying VALUE_PAIRs. + * + * @param[in] t The talloc context to hang the result off. + * @param[in] fmt The format string. + * @return The formatted string, NULL on error. + */ +char *talloc_typed_asprintf(void const *t, char const *fmt, ...) +{ + char *n; + va_list ap; + + va_start(ap, fmt); + n = talloc_vasprintf(t, fmt, ap); + va_end(ap); + if (!n) return NULL; + talloc_set_type(n, char); + + return n; +} + +/** Binary safe strndup function + * + * @param[in] t The talloc context o allocate new buffer in. + * @param[in] in String to dup, may contain embedded '\0'. + * @param[in] inlen Number of bytes to dup. + * @return duped string. + */ +char *talloc_bstrndup(void const *t, char const *in, size_t inlen) +{ + char *p; + + p = talloc_array(t, char, inlen + 1); + if (!p) return NULL; + memcpy(p, in, inlen); + p[inlen] = '\0'; + + return p; +} + diff --git a/src/lib/net.c b/src/lib/net.c new file mode 100644 index 0000000..7c899d7 --- /dev/null +++ b/src/lib/net.c @@ -0,0 +1,94 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 of the + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file net.c + * @brief Functions to parse raw packets. + * + * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org> + * @copyright 2014-2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org> + */ + #include <freeradius-devel/libradius.h> + #include <freeradius-devel/net.h> + +/** Calculate UDP checksum + * + * Zero out UDP checksum in UDP header before calling fr_udp_checksum to get 'expected' checksum. + * + * @param data Pointer to the start of the UDP header + * @param len value of udp length field in host byte order. Must be validated to make + * sure it won't overrun data buffer. + * @param checksum current checksum, leave as 0 to just enable validation. + * @param src_addr in network byte order. + * @param dst_addr in network byte order. + * @return 0 if the checksum is correct, else another number. + */ +uint16_t fr_udp_checksum(uint8_t const *data, uint16_t len, uint16_t checksum, + struct in_addr const src_addr, struct in_addr const dst_addr) +{ + uint64_t sum = 0; /* using 64bits avoids overflow check */ + uint16_t const *p = (uint16_t const *)data; + + uint16_t const *ip_src = (void const *) &src_addr.s_addr; + uint16_t const *ip_dst = (void const *) &dst_addr.s_addr; + uint16_t i; + + sum += *(ip_src++); + sum += *ip_src; + sum += *(ip_dst++); + sum += *ip_dst; + + sum += htons(IPPROTO_UDP); + sum += htons(len); + + for (i = len; i > 1; i -= 2) { + sum += *p++; + } + + if (i) { + sum += (0xff & *(uint8_t const *)p) << 8; + } + + sum -= checksum; + + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + + return ((uint16_t) ~sum); +} + +/** Calculate IP header checksum. + * + * Zero out IP header checksum in IP header before calling fr_iph_checksum to get 'expected' checksum. + * + * @param data Pointer to the start of the IP header + * @param ihl value of ip header length field (number of 32 bit words) + */ +uint16_t fr_iph_checksum(uint8_t const *data, uint8_t ihl) +{ + uint64_t sum = 0; + uint16_t const *p = (uint16_t const *)data; + + uint8_t nwords = (ihl << 1); /* number of 16-bit words */ + + for (sum = 0; nwords > 0; nwords--) { + sum += *p++; + } + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + return ((uint16_t) ~sum); +} diff --git a/src/lib/packet.c b/src/lib/packet.c new file mode 100644 index 0000000..971980b --- /dev/null +++ b/src/lib/packet.c @@ -0,0 +1,1116 @@ +/* + * packet.c Generic packet manipulation functions. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000-2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#ifdef WITH_UDPFROMTO +#include <freeradius-devel/udpfromto.h> +#endif + +#include <fcntl.h> + +/* + * See if two packets are identical. + * + * Note that we do NOT compare the authentication vectors. + * That's because if the authentication vector is different, + * it means that the NAS has given up on the earlier request. + */ +int fr_packet_cmp(RADIUS_PACKET const *a, RADIUS_PACKET const *b) +{ + int rcode; + + if (a->sockfd < b->sockfd) return -1; + if (a->sockfd > b->sockfd) return +1; + + /* + * IDs should be spread effectively randomly + */ + if (a->id < b->id) return -1; + if (a->id > b->id) return +1; + +#ifdef WITH_TCP + /* + * TCP sockets have (by definition) the same src/dst + * IP/port combos. We we can just ignore those fields. + */ + if (a->proto == IPPROTO_TCP) { + return 0; + } +#endif + + /* + * RADIUS can have unconnected UDP sockets, in which case + * the socket FD is the same, but the src/dst IP/ports + * may be different. + */ + + /* + * Source ports are pretty much random. + */ + rcode = (int) a->src_port - (int) b->src_port; + if (rcode != 0) return rcode; + + /* + * Usually many client IPs, and few server IPs + */ + rcode = fr_ipaddr_cmp(&a->src_ipaddr, &b->src_ipaddr); + if (rcode != 0) return rcode; + + /* + * One socket can receive packets for multiple + * destination IPs, so we check that before checking the + * file descriptor. + */ + rcode = fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr); + if (rcode != 0) return rcode; + + /* + * At this point, the order of comparing socket FDs + * and/or destination ports doesn't matter. One of those + * fields will make the socket unique, and the other is + * pretty much redundant. + */ + rcode = (int) a->dst_port - (int) b->dst_port; + return rcode; +} + +int fr_inaddr_any(fr_ipaddr_t *ipaddr) +{ + + if (ipaddr->af == AF_INET) { + if (ipaddr->ipaddr.ip4addr.s_addr == INADDR_ANY) { + return 1; + } + +#ifdef HAVE_STRUCT_SOCKADDR_IN6 + } else if (ipaddr->af == AF_INET6) { + if (IN6_IS_ADDR_UNSPECIFIED(&(ipaddr->ipaddr.ip6addr))) { + return 1; + } +#endif + + } else { + fr_strerror_printf("Unknown address family"); + return -1; + } + + return 0; +} + + +/* + * Create a fake "request" from a reply, for later lookup. + */ +void fr_request_from_reply(RADIUS_PACKET *request, + RADIUS_PACKET const *reply) +{ + request->sockfd = reply->sockfd; + request->id = reply->id; +#ifdef WITH_TCP + request->proto = reply->proto; + +#ifdef WITH_RADIUSV11 + if (reply->radiusv11) { + memcpy(request->vector, reply->vector, sizeof(request->vector)); + } +#endif +#endif + request->src_port = reply->dst_port; + request->dst_port = reply->src_port; + request->src_ipaddr = reply->dst_ipaddr; + request->dst_ipaddr = reply->src_ipaddr; +} + +/* + * Open a socket on the given IP and port. + */ +int fr_socket(fr_ipaddr_t *ipaddr, uint16_t port) +{ + int sockfd; + struct sockaddr_storage salocal; + socklen_t salen; + + sockfd = socket(ipaddr->af, SOCK_DGRAM, 0); + if (sockfd < 0) { + fr_strerror_printf("cannot open socket: %s", fr_syserror(errno)); + return sockfd; + } + +#ifdef WITH_UDPFROMTO + /* + * Initialize udpfromto for all sockets. + */ + if (udpfromto_init(sockfd) != 0) { + close(sockfd); + fr_strerror_printf("cannot initialize udpfromto: %s", fr_syserror(errno)); + return -1; + } +#endif + + if (!fr_ipaddr2sockaddr(ipaddr, port, &salocal, &salen)) { + return sockfd; + } + +#ifdef HAVE_STRUCT_SOCKADDR_IN6 + if (ipaddr->af == AF_INET6) { + /* + * Listening on '::' does NOT get you IPv4 to + * IPv6 mapping. You've got to listen on an IPv4 + * address, too. This makes the rest of the server + * design a little simpler. + */ +#ifdef IPV6_V6ONLY + + if (IN6_IS_ADDR_UNSPECIFIED(&ipaddr->ipaddr.ip6addr)) { + int on = 1; + + if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&on, sizeof(on)) < 0) { + close(sockfd); + fr_strerror_printf("Failed setting sockopt " + "IPPROTO_IPV6 - IPV6_V6ONLY" + ": %s", fr_syserror(errno)); + return -1; + } + } +#endif /* IPV6_V6ONLY */ + } +#endif /* HAVE_STRUCT_SOCKADDR_IN6 */ + +#if (defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)) || defined(IP_DONTFRAG) + if (ipaddr->af == AF_INET) { + int flag; + +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) + + /* + * Disable PMTU discovery. On Linux, this + * also makes sure that the "don't fragment" + * flag is zero. + */ + flag = IP_PMTUDISC_DONT; + if (setsockopt(sockfd, IPPROTO_IP, IP_MTU_DISCOVER, + &flag, sizeof(flag)) < 0) { + close(sockfd); + fr_strerror_printf("Failed setting sockopt " + "IPPROTO_IP - IP_MTU_DISCOVER: %s", + fr_syserror(errno)); + return -1; + } +#endif + +#if defined(IP_DONTFRAG) + /* + * Ensure that the "don't fragment" flag is zero. + */ + flag = 0; + if (setsockopt(sockfd, IPPROTO_IP, IP_DONTFRAG, + &flag, sizeof(flag)) < 0) { + close(sockfd); + fr_strerror_printf("Failed setting sockopt " + "IPPROTO_IP - IP_DONTFRAG: %s", + fr_syserror(errno)); + return -1; + } +#endif + } +#endif + + if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) { + close(sockfd); + fr_strerror_printf("cannot bind socket: %s", fr_syserror(errno)); + return -1; + } + + return sockfd; +} + + +/* + * We need to keep track of the socket & it's IP/port. + */ +typedef struct fr_packet_socket_t { + int sockfd; + void *ctx; + + uint32_t num_outgoing; + + int src_any; + fr_ipaddr_t src_ipaddr; + uint16_t src_port; + + int dst_any; + fr_ipaddr_t dst_ipaddr; + uint16_t dst_port; + + bool dont_use; + +#ifdef WITH_TCP + int proto; +#endif + +#ifdef WITH_RADIUSV11 + bool radiusv11; + + uint32_t counter; +#endif + + uint8_t id[32]; +} fr_packet_socket_t; + + +#define FNV_MAGIC_PRIME (0x01000193) +#define MAX_SOCKETS (1024) +#define SOCKOFFSET_MASK (MAX_SOCKETS - 1) +#define SOCK2OFFSET(sockfd) ((sockfd * FNV_MAGIC_PRIME) & SOCKOFFSET_MASK) + +/* + * Structure defining a list of packets (incoming or outgoing) + * that should be managed. + */ +struct fr_packet_list_t { + rbtree_t *tree; + + int alloc_id; + uint32_t num_outgoing; + int last_recv; + int num_sockets; + + fr_packet_socket_t sockets[MAX_SOCKETS]; +}; + + +/* + * Ugh. Doing this on every sent/received packet is not nice. + */ +static fr_packet_socket_t *fr_socket_find(fr_packet_list_t *pl, + int sockfd) +{ + int i, start; + + i = start = SOCK2OFFSET(sockfd); + + do { /* make this hack slightly more efficient */ + if (pl->sockets[i].sockfd == sockfd) return &pl->sockets[i]; + + i = (i + 1) & SOCKOFFSET_MASK; + } while (i != start); + + return NULL; +} + +bool fr_packet_list_socket_freeze(fr_packet_list_t *pl, int sockfd) +{ + fr_packet_socket_t *ps; + + if (!pl) { + fr_strerror_printf("Invalid argument"); + return false; + } + + ps = fr_socket_find(pl, sockfd); + if (!ps) { + fr_strerror_printf("No such socket"); + return false; + } + + ps->dont_use = true; + return true; +} + +bool fr_packet_list_socket_thaw(fr_packet_list_t *pl, int sockfd) +{ + fr_packet_socket_t *ps; + + if (!pl) return false; + + ps = fr_socket_find(pl, sockfd); + if (!ps) return false; + + ps->dont_use = false; + return true; +} + + +bool fr_packet_list_socket_del(fr_packet_list_t *pl, int sockfd) +{ + fr_packet_socket_t *ps; + + if (!pl) return false; + + ps = fr_socket_find(pl, sockfd); + if (!ps) return false; + + if (ps->num_outgoing != 0) { + fr_strerror_printf("socket is still in use"); + return false; + } + + ps->sockfd = -1; + pl->num_sockets--; + + return true; +} + + +bool fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd, int proto, +#ifdef WITH_RADIUSV11 + bool radiusv11, +#endif + fr_ipaddr_t *dst_ipaddr, uint16_t dst_port, + void *ctx) +{ + int i, start; + struct sockaddr_storage src; + socklen_t sizeof_src; + fr_packet_socket_t *ps; + + if (!pl || !dst_ipaddr || (dst_ipaddr->af == AF_UNSPEC)) { + fr_strerror_printf("Invalid argument"); + return false; + } + + if (pl->num_sockets >= MAX_SOCKETS) { + fr_strerror_printf("Too many open sockets"); + return false; + } + +#ifndef WITH_TCP + if (proto != IPPROTO_UDP) { + fr_strerror_printf("only UDP is supported"); + return false; + } +#endif + + ps = NULL; + i = start = SOCK2OFFSET(sockfd); + + do { + if (pl->sockets[i].sockfd == -1) { + ps = &pl->sockets[i]; + break; + } + + i = (i + 1) & SOCKOFFSET_MASK; + } while (i != start); + + if (!ps) { + fr_strerror_printf("All socket entries are full"); + return false; + } + + memset(ps, 0, sizeof(*ps)); + ps->ctx = ctx; +#ifdef WITH_TCP + ps->proto = proto; +#ifdef WITH_RADIUSV11 + ps->radiusv11 = radiusv11; + + /* + * Start our packet counter at a random 64-bit number. + */ + if (radiusv11) { + ps->counter = fr_rand(); + } +#endif +#endif + + /* + * Get address family, etc. first, so we know if we + * need to do udpfromto. + * + * FIXME: udpfromto also does this, but it's not + * a critical problem. + */ + sizeof_src = sizeof(src); + memset(&src, 0, sizeof_src); + if (getsockname(sockfd, (struct sockaddr *) &src, + &sizeof_src) < 0) { + fr_strerror_printf("%s", fr_syserror(errno)); + return false; + } + + if (!fr_sockaddr2ipaddr(&src, sizeof_src, &ps->src_ipaddr, + &ps->src_port)) { + fr_strerror_printf("Failed to get IP"); + return false; + } + + ps->dst_ipaddr = *dst_ipaddr; + ps->dst_port = dst_port; + + ps->src_any = fr_inaddr_any(&ps->src_ipaddr); + if (ps->src_any < 0) return false; + + ps->dst_any = fr_inaddr_any(&ps->dst_ipaddr); + if (ps->dst_any < 0) return false; + + /* + * As the last step before returning. + */ + ps->sockfd = sockfd; + pl->num_sockets++; + + return true; +} + +static int packet_entry_cmp(void const *one, void const *two) +{ + RADIUS_PACKET const * const *a = one; + RADIUS_PACKET const * const *b = two; + + return fr_packet_cmp(*a, *b); +} + +void fr_packet_list_free(fr_packet_list_t *pl) +{ + if (!pl) return; + + rbtree_free(pl->tree); + free(pl); +} + + +/* + * Caller is responsible for managing the packet entries. + */ +fr_packet_list_t *fr_packet_list_create(int alloc_id) +{ + int i; + fr_packet_list_t *pl; + + pl = malloc(sizeof(*pl)); + if (!pl) return NULL; + memset(pl, 0, sizeof(*pl)); + + pl->tree = rbtree_create(NULL, packet_entry_cmp, NULL, 0); + if (!pl->tree) { + fr_packet_list_free(pl); + return NULL; + } + + for (i = 0; i < MAX_SOCKETS; i++) { + pl->sockets[i].sockfd = -1; + } + + pl->alloc_id = alloc_id; + + return pl; +} + + +/* + * If pl->alloc_id is set, then fr_packet_list_id_alloc() MUST + * be called before inserting the packet into the list! + */ +bool fr_packet_list_insert(fr_packet_list_t *pl, + RADIUS_PACKET **request_p) +{ + if (!pl || !request_p || !*request_p) return 0; + + VERIFY_PACKET(*request_p); + + return rbtree_insert(pl->tree, request_p); +} + +RADIUS_PACKET **fr_packet_list_find(fr_packet_list_t *pl, + RADIUS_PACKET *request) +{ + if (!pl || !request) return 0; + + VERIFY_PACKET(request); + + return rbtree_finddata(pl->tree, &request); +} + + +/* + * This presumes that the reply has dst_ipaddr && dst_port set up + * correctly (i.e. real IP, or "*"). + */ +RADIUS_PACKET **fr_packet_list_find_byreply(fr_packet_list_t *pl, + RADIUS_PACKET *reply) +{ + RADIUS_PACKET my_request, *request; + fr_packet_socket_t *ps; + + if (!pl || !reply) return NULL; + + VERIFY_PACKET(reply); + + ps = fr_socket_find(pl, reply->sockfd); + if (!ps) return NULL; + + /* + * Initialize request from reply, AND from the source + * IP & port of this socket. The client may have bound + * the socket to 0, in which case it's some random port, + * that is NOT in the original request->src_port. + */ + my_request.sockfd = reply->sockfd; + my_request.id = reply->id; + +#ifdef WITH_TCP + /* + * TCP sockets are always bound to the correct src/dst IP/port + */ + if (ps->proto == IPPROTO_TCP) { + reply->dst_ipaddr = ps->src_ipaddr; + reply->dst_port = ps->src_port; + reply->src_ipaddr = ps->dst_ipaddr; + reply->src_port = ps->dst_port; + + my_request.src_ipaddr = ps->src_ipaddr; + my_request.src_port = ps->src_port; + my_request.dst_ipaddr = ps->dst_ipaddr; + my_request.dst_port = ps->dst_port; + + } else +#endif + { + if (ps->src_any) { + my_request.src_ipaddr = ps->src_ipaddr; + } else { + my_request.src_ipaddr = reply->dst_ipaddr; + } + my_request.src_port = ps->src_port; + + my_request.dst_ipaddr = reply->src_ipaddr; + my_request.dst_port = reply->src_port; + } + +#ifdef WITH_TCP + my_request.proto = reply->proto; +#endif + +#ifdef WITH_RADIUSV11 + my_request.radiusv11 = reply->radiusv11; +#endif + + request = &my_request; + + return rbtree_finddata(pl->tree, &request); +} + + +bool fr_packet_list_yank(fr_packet_list_t *pl, RADIUS_PACKET *request) +{ + rbnode_t *node; + + if (!pl || !request) return false; + + VERIFY_PACKET(request); + + node = rbtree_find(pl->tree, &request); + if (!node) return false; + + rbtree_delete(pl->tree, node); + return true; +} + +uint32_t fr_packet_list_num_elements(fr_packet_list_t *pl) +{ + if (!pl) return 0; + + return rbtree_num_elements(pl->tree); +} + + +/* + * 1 == ID was allocated & assigned + * 0 == couldn't allocate ID. + * + * Note that this ALSO assigns a socket to use, and updates + * packet->request->src_ipaddr && packet->request->src_port + * + * In multi-threaded systems, the calls to id_alloc && id_free + * should be protected by a mutex. This does NOT have to be + * the same mutex as the one protecting the insert/find/yank + * calls! + * + * We assume that the packet has dst_ipaddr && dst_port + * already initialized. We will use those to find an + * outgoing socket. The request MAY also have src_ipaddr set. + * + * We also assume that the sender doesn't care which protocol + * should be used. + */ +bool fr_packet_list_id_alloc(fr_packet_list_t *pl, int proto, + RADIUS_PACKET **request_p, void **pctx) +{ + int i, j, k, fd, id, start_i, start_j, start_k; + int src_any = 0; + fr_packet_socket_t *ps= NULL; + RADIUS_PACKET *request = *request_p; + + VERIFY_PACKET(request); + + if ((request->dst_ipaddr.af == AF_UNSPEC) || + (request->dst_port == 0)) { + fr_strerror_printf("No destination address/port specified"); + return false; + } + +#ifndef WITH_TCP + if ((proto != 0) && (proto != IPPROTO_UDP)) { + fr_strerror_printf("Invalid destination protocol"); + return false; + } +#endif + + /* + * Special case: unspec == "don't care" + */ + if (request->src_ipaddr.af == AF_UNSPEC) { + memset(&request->src_ipaddr, 0, sizeof(request->src_ipaddr)); + request->src_ipaddr.af = request->dst_ipaddr.af; + } + + src_any = fr_inaddr_any(&request->src_ipaddr); + if (src_any < 0) { + fr_strerror_printf("Can't check src_ipaddr"); + return false; + } + + /* + * MUST specify a destination address. + */ + if (fr_inaddr_any(&request->dst_ipaddr) != 0) { + fr_strerror_printf("Must specify a dst_ipaddr"); + return false; + } + + /* + * FIXME: Go to an LRU system. This prevents ID re-use + * for as long as possible. The main problem with that + * approach is that it requires us to populate the + * LRU/FIFO when we add a new socket, or a new destination, + * which can be expensive. + * + * The LRU can be avoided if the caller takes care to free + * Id's only when all responses have been received, OR after + * a timeout. + * + * Right now, the random approach is almost OK... it's + * brute-force over all of the available ID's, BUT using + * random numbers for everything spreads the load a bit. + * + * The old method had a hash lookup on allocation AND + * on free. The new method has brute-force on allocation, + * and near-zero cost on free. + */ + + id = fd = -1; + start_i = fr_rand() & SOCKOFFSET_MASK; + +#define ID_i ((i + start_i) & SOCKOFFSET_MASK) + for (i = 0; i < MAX_SOCKETS; i++) { + if (pl->sockets[ID_i].sockfd == -1) continue; /* paranoia */ + + ps = &(pl->sockets[ID_i]); + + /* + * This socket is marked as "don't use for new + * packets". But we can still receive packets + * that are outstanding. + */ + if (ps->dont_use) continue; + +#ifdef WITH_TCP + if (ps->proto != proto) continue; +#endif + + /* + * Address families don't match, skip it. + */ + if (ps->src_ipaddr.af != request->dst_ipaddr.af) continue; + + /* + * MUST match dst port, if we have one. + */ + if ((ps->dst_port != 0) && + (ps->dst_port != request->dst_port)) continue; + + /* + * MUST match requested src port, if one has been given. + */ + if ((request->src_port != 0) && + (ps->src_port != request->src_port)) continue; + + /* + * We don't care about the source IP, but this + * socket is link local, and the requested + * destination is not link local. Ignore it. + */ + if (src_any && (ps->src_ipaddr.af == AF_INET) && + (((ps->src_ipaddr.ipaddr.ip4addr.s_addr >> 24) & 0xff) == 127) && + (((request->dst_ipaddr.ipaddr.ip4addr.s_addr >> 24) & 0xff) != 127)) continue; + + /* + * We're sourcing from *, and they asked for a + * specific source address: ignore it. + */ + if (ps->src_any && !src_any) continue; + + /* + * We're sourcing from a specific IP, and they + * asked for a source IP that isn't us: ignore + * it. + */ + if (!ps->src_any && !src_any && + (fr_ipaddr_cmp(&request->src_ipaddr, + &ps->src_ipaddr) != 0)) continue; + + /* + * UDP sockets are allowed to match + * destination IPs exactly, OR a socket + * with destination * is allowed to match + * any requested destination. + * + * TCP sockets must match the destination + * exactly. They *always* have dst_any=0, + * so the first check always matches. + */ + if (!ps->dst_any && + (fr_ipaddr_cmp(&request->dst_ipaddr, + &ps->dst_ipaddr) != 0)) continue; + +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 matches on src/dst IP/port, but + * doesn't care about num_outgoing or ID allocation. + */ + if (ps->radiusv11) { + request->radiusv11 = true; + + fd = ps->sockfd; + + /* + * Increment the counter. It has to be + * unique, but its exact value doesn't + * matter. i.e. the receiver doesn't + * care if it's a counter or a random + * number. + */ + ps->counter++; + id = 0; + memcpy(request->vector, &ps->counter, sizeof(ps->counter)); + memset(request->vector + sizeof(ps->counter), 0, sizeof(request->vector) - sizeof(ps->counter)); + break; + } +#endif + + /* + * All IDs are allocated: ignore it. + */ + if (ps->num_outgoing == 256) continue; + + /* + * Otherwise, this socket is OK to use. + */ + + /* + * Look for a free Id, starting from a random number. + */ + start_j = fr_rand() & 0x1f; +#define ID_j ((j + start_j) & 0x1f) + for (j = 0; j < 32; j++) { + if (ps->id[ID_j] == 0xff) continue; + + + start_k = fr_rand() & 0x07; +#define ID_k ((k + start_k) & 0x07) + for (k = 0; k < 8; k++) { + if ((ps->id[ID_j] & (1 << ID_k)) != 0) continue; + + ps->id[ID_j] |= (1 << ID_k); + id = (ID_j * 8) + ID_k; + fd = i; + break; + } + if (fd >= 0) break; + } +#undef ID_i +#undef ID_j +#undef ID_k + if (fd >= 0) break; + } + + /* + * Ask the caller to allocate a new ID. + */ + if (fd < 0) { + fr_strerror_printf("Failed finding socket, caller must allocate a new one"); + return false; + } + + /* + * Set the ID, source IP, and source port. + */ + request->id = id; +#ifdef WITH_RADIUSV11 + if (ps->radiusv11) request->id = ps->counter; +#endif + + request->sockfd = ps->sockfd; + request->src_ipaddr = ps->src_ipaddr; + request->src_port = ps->src_port; + + /* + * If we managed to insert it, we're done. + */ + if (fr_packet_list_insert(pl, request_p)) { + if (pctx) *pctx = ps->ctx; + ps->num_outgoing++; + pl->num_outgoing++; + return true; + } + + /* + * Mark the ID as free. This is the one line from + * id_free() that we care about here. + */ + ps->id[(id >> 3) & 0x1f] &= ~(1 << (id & 0x07)); + + request->id = -1; + request->sockfd = -1; + request->src_ipaddr.af = AF_UNSPEC; + request->src_port = 0; + + return false; +} + +/* + * Should be called AFTER yanking it from the list, so that + * any newly inserted entries don't collide with this one. + */ +bool fr_packet_list_id_free(fr_packet_list_t *pl, + RADIUS_PACKET *request, bool yank) +{ + fr_packet_socket_t *ps; + + if (!pl || !request) return false; + + VERIFY_PACKET(request); + + if (yank && !fr_packet_list_yank(pl, request)) return false; + + ps = fr_socket_find(pl, request->sockfd); + if (!ps) return false; + +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 packets don't track individual IDs. + */ + if (!ps->radiusv11) +#endif + ps->id[(request->id >> 3) & 0x1f] &= ~(1 << (request->id & 0x07)); + + ps->num_outgoing--; + pl->num_outgoing--; + + request->id = -1; + request->src_ipaddr.af = AF_UNSPEC; /* id_alloc checks this */ + request->src_port = 0; + + return true; +} + +/* + * We always walk RBTREE_DELETE_ORDER, which is like RBTREE_IN_ORDER, except that + * <0 means error, stop + * 0 means OK, continue + * 1 means delete current node and stop + * 2 means delete current node and continue + */ +int fr_packet_list_walk(fr_packet_list_t *pl, void *ctx, rb_walker_t callback) +{ + if (!pl || !callback) return 0; + + return rbtree_walk(pl->tree, RBTREE_DELETE_ORDER, callback, ctx); +} + +int fr_packet_list_fd_set(fr_packet_list_t *pl, fd_set *set) +{ + int i, maxfd; + + if (!pl || !set) return 0; + + maxfd = -1; + + for (i = 0; i < MAX_SOCKETS; i++) { + if (pl->sockets[i].sockfd == -1) continue; + FD_SET(pl->sockets[i].sockfd, set); + if (pl->sockets[i].sockfd > maxfd) { + maxfd = pl->sockets[i].sockfd; + } + } + + if (maxfd < 0) return -1; + + return maxfd + 1; +} + +/* + * Round-robins the receivers, without priority. + * + * FIXME: Add sockfd, if -1, do round-robin, else do sockfd + * IF in fdset. + */ +RADIUS_PACKET *fr_packet_list_recv(fr_packet_list_t *pl, fd_set *set) +{ + int start; + RADIUS_PACKET *packet; + + if (!pl || !set) return NULL; + + start = pl->last_recv; + do { + start++; + start &= SOCKOFFSET_MASK; + + if (pl->sockets[start].sockfd == -1) continue; + + if (!FD_ISSET(pl->sockets[start].sockfd, set)) continue; + +#ifdef WITH_TCP + if (pl->sockets[start].proto == IPPROTO_TCP) { + packet = fr_tcp_recv(pl->sockets[start].sockfd, 0); + if (!packet) { + fr_strerror_printf("TCP connection has been closed"); + return NULL; + } + + /* + * We always know src/dst ip/port for TCP + * sockets. So just fill them in. Since + * we read the packet from the TCP + * socket, we invert src/dst. + */ + packet->dst_ipaddr = pl->sockets[start].src_ipaddr; + packet->dst_port = pl->sockets[start].src_port; + packet->src_ipaddr = pl->sockets[start].dst_ipaddr; + packet->src_port = pl->sockets[start].dst_port; + + } else +#endif + + /* + * Rely on rad_recv() to fill in the required + * fields. + */ + packet = rad_recv(NULL, pl->sockets[start].sockfd, 0); + if (!packet) continue; + + /* + * Call fr_packet_list_find_byreply(). If it + * doesn't find anything, discard the reply. + */ + + pl->last_recv = start; +#ifdef WITH_TCP + packet->proto = pl->sockets[start].proto; +#endif + return packet; + } while (start != pl->last_recv); + + return NULL; +} + +uint32_t fr_packet_list_num_incoming(fr_packet_list_t *pl) +{ + uint32_t num_elements; + + if (!pl) return 0; + + num_elements = rbtree_num_elements(pl->tree); + if (num_elements < pl->num_outgoing) return 0; /* panic! */ + + return num_elements - pl->num_outgoing; +} + +uint32_t fr_packet_list_num_outgoing(fr_packet_list_t *pl) +{ + if (!pl) return 0; + + return pl->num_outgoing; +} + +/* + * Debug the packet if requested. + */ +void fr_packet_header_print(FILE *fp, RADIUS_PACKET *packet, bool received) +{ + char src_ipaddr[128]; + char dst_ipaddr[128]; + + if (!fp) return; + if (!packet) return; + + /* + * Client-specific debugging re-prints the input + * packet into the client log. + * + * This really belongs in a utility library + */ + if (is_radius_code(packet->code)) { + fprintf(fp, "%s %s Id %i from %s%s%s:%x to %s%s%s:%u length %zu\n", + received ? "Received" : "Sent", + fr_packet_codes[packet->code], + packet->id, + packet->src_ipaddr.af == AF_INET6 ? "[" : "", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + src_ipaddr, sizeof(src_ipaddr)), + packet->src_ipaddr.af == AF_INET6 ? "]" : "", + packet->src_port, + packet->dst_ipaddr.af == AF_INET6 ? "[" : "", + inet_ntop(packet->dst_ipaddr.af, + &packet->dst_ipaddr.ipaddr, + dst_ipaddr, sizeof(dst_ipaddr)), + packet->dst_ipaddr.af == AF_INET6 ? "]" : "", + packet->dst_port, + packet->data_len); + } else { + fprintf(fp, "%s code %u Id %i from %s%s%s:%u to %s%s%s:%i length %zu\n", + received ? "Received" : "Sent", + packet->code, + packet->id, + packet->src_ipaddr.af == AF_INET6 ? "[" : "", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + src_ipaddr, sizeof(src_ipaddr)), + packet->src_ipaddr.af == AF_INET6 ? "]" : "", + packet->src_port, + packet->dst_ipaddr.af == AF_INET6 ? "[" : "", + inet_ntop(packet->dst_ipaddr.af, + &packet->dst_ipaddr.ipaddr, + dst_ipaddr, sizeof(dst_ipaddr)), + packet->dst_ipaddr.af == AF_INET6 ? "]" : "", + packet->dst_port, + packet->data_len); + } +} + diff --git a/src/lib/pair.c b/src/lib/pair.c new file mode 100644 index 0000000..449e0e1 --- /dev/null +++ b/src/lib/pair.c @@ -0,0 +1,2520 @@ +/* + * pair.c Functions to handle VALUE_PAIRs + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/regex.h> + +#include <ctype.h> + +/** Free a VALUE_PAIR + * + * @note Do not call directly, use talloc_free instead. + * + * @param vp to free. + * @return 0 + */ +static int _fr_pair_free(VALUE_PAIR *vp) { +#ifndef NDEBUG + vp->vp_integer = 0xf4eef4ee; +#endif + +#ifdef TALLOC_DEBUG + talloc_report_depth_cb(NULL, 0, -1, fr_talloc_verify_cb, NULL); +#endif + return 0; +} + +VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx) +{ + VALUE_PAIR *vp; + + vp = talloc_zero(ctx, VALUE_PAIR); + if (!vp) { + fr_strerror_printf("Out of memory"); + return NULL; + } + + vp->op = T_OP_EQ; + vp->tag = TAG_ANY; + vp->type = VT_NONE; + + talloc_set_destructor(vp, _fr_pair_free); + + return vp; +} + + +/** Dynamically allocate a new attribute + * + * Allocates a new attribute and a new dictionary attr if no DA is provided. + * + * @param[in] ctx for allocated memory, usually a pointer to a RADIUS_PACKET + * @param[in] da Specifies the dictionary attribute to build the VP from. + * @return a new value pair or NULL if an error occurred. + */ +VALUE_PAIR *fr_pair_afrom_da(TALLOC_CTX *ctx, DICT_ATTR const *da) +{ + VALUE_PAIR *vp; + + /* + * Caller must specify a da else we don't know what the attribute type is. + */ + if (!da) { + fr_strerror_printf("Invalid arguments"); + return NULL; + } + + vp = fr_pair_alloc(ctx); + if (!vp) { + fr_strerror_printf("Out of memory"); + return NULL; + } + + /* + * Use the 'da' to initialize more fields. + */ + vp->da = da; + vp->vp_length = da->flags.length; + + return vp; +} + +/** Create a new valuepair + * + * If attr and vendor match a dictionary entry then a VP with that DICT_ATTR + * will be returned. + * + * If attr or vendor are uknown will call dict_attruknown to create a dynamic + * DICT_ATTR of PW_TYPE_OCTETS. + * + * Which type of DICT_ATTR the VALUE_PAIR was created with can be determined by + * checking @verbatim vp->da->flags.is_unknown @endverbatim. + * + * @param[in] ctx for allocated memory, usually a pointer to a RADIUS_PACKET + * @param[in] attr number. + * @param[in] vendor number. + * @return the new valuepair or NULL on error. + */ +VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int vendor) +{ + DICT_ATTR const *da; + + da = dict_attrbyvalue(attr, vendor); + if (!da) return NULL; + + return fr_pair_afrom_da(ctx, da); +} + +/** Free memory used by a valuepair list. + * + * @todo TLV: needs to free all dependents of each VP freed. + */ +void fr_pair_list_free(VALUE_PAIR **vps) +{ + VALUE_PAIR *vp; + vp_cursor_t cursor; + + if (!vps || !*vps) { + return; + } + + for (vp = fr_cursor_init(&cursor, vps); + vp; + vp = fr_cursor_next(&cursor)) { + VERIFY_VP(vp); + talloc_free(vp); + } + + *vps = NULL; +} + +/** Mark malformed or unrecognised attributed as unknown + * + * @param vp to change DICT_ATTR of. + * @return 0 on success (or if already unknown) else -1 on error. + */ +int fr_pair_to_unknown(VALUE_PAIR *vp) +{ + DICT_ATTR const *da; + + VERIFY_VP(vp); + if (vp->da->flags.is_unknown) { + return 0; + } + + da = dict_unknown_afrom_fields(vp, vp->da->attr, vp->da->vendor); + if (!da) { + return -1; + } + + vp->da = da; + + return 0; +} + +/** Find the pair with the matching DAs + * + */ +VALUE_PAIR *fr_pair_find_by_da(VALUE_PAIR *vp, DICT_ATTR const *da, int8_t tag) +{ + vp_cursor_t cursor; + + if(!fr_assert(da)) { + return NULL; + } + + (void) fr_cursor_init(&cursor, &vp); + return fr_cursor_next_by_da(&cursor, da, tag); +} + + +/** Find the pair with the matching attribute + * + * @todo should take DAs and do a pointer comparison. + */ +VALUE_PAIR *fr_pair_find_by_num(VALUE_PAIR *vp, unsigned int attr, unsigned int vendor, int8_t tag) +{ + vp_cursor_t cursor; + + /* List head may be NULL if it contains no VPs */ + if (!vp) return NULL; + + VERIFY_LIST(vp, ""); + + (void) fr_cursor_init(&cursor, &vp); + return fr_cursor_next_by_num(&cursor, attr, vendor, tag); +} + +/** Delete matching pairs + * + * Delete matching pairs from the attribute list. + * + * @param[in,out] first VP in list. + * @param[in] attr to match. + * @param[in] vendor to match. + * @param[in] tag to match. TAG_ANY matches any tag, TAG_NONE matches tagless VPs. + * + * @todo should take DAs and do a point comparison. + */ +void fr_pair_delete_by_num(VALUE_PAIR **first, unsigned int attr, unsigned int vendor, int8_t tag) +{ + VALUE_PAIR *i, *next; + VALUE_PAIR **last = first; + + for(i = *first; i; i = next) { + VERIFY_VP(i); + next = i->next; + if ((i->da->attr == attr) && (i->da->vendor == vendor) && + (!i->da->flags.has_tag || TAG_EQ(tag, i->tag))) { + *last = next; + talloc_free(i); + } else { + last = &i->next; + } + } +} + +/** Delete matching pairs by da + * + * Delete matching pairs from the attribute list. + * + * @param[in,out] first VP in list. + * @param[in] da to match. + */ +void fr_pair_delete_by_da(VALUE_PAIR **first, DICT_ATTR const *da) +{ + VALUE_PAIR *i, *next; + VALUE_PAIR **last = first; + + for(i = *first; i; i = next) { + VERIFY_VP(i); + next = i->next; + if (i->da == da) { + *last = next; + talloc_free(i); + } else { + last = &i->next; + } + } +} + +/** Add a VP to the end of the list. + * + * Locates the end of 'first', and links an additional VP 'add' at the end. + * + * @param[in] first VP in linked list. Will add new VP to the end of this list. + * @param[in] add VP to add to list. + */ +void fr_pair_add(VALUE_PAIR **first, VALUE_PAIR *add) +{ + VALUE_PAIR *i; + + if (!add) return; + + VERIFY_VP(add); + + if (*first == NULL) { + *first = add; + return; + } + + for (i = *first; i->next; i = i->next) { +#ifdef WITH_VERIFY_PTR + VERIFY_VP(i); + /* + * The same VP should never by added multiple times + * to the same list. + */ + fr_assert(i != add); +#endif + } + + i->next = add; +} + +/** Add a VP to the start of the list. + * + * Links the new VP to 'first', then points 'first' at the new VP. + * + * @param[in] first VP in linked list. Will add new VP to the start of this list. + * @param[in] add VP to add to list. + */ +void fr_pair_prepend(VALUE_PAIR **first, VALUE_PAIR *add) +{ + VALUE_PAIR *i; + + if (!add) return; + + VERIFY_VP(add); + + if (*first == NULL) { + *first = add; + return; + } + + /* + * Find the end of the list to be prepended + */ + for (i = add; i->next; i = i->next) { +#ifdef WITH_VERIFY_PTR + VERIFY_VP(i); + /* + * The same VP should never by added multiple times + * to the same list. + */ + fr_assert(*first != i); +#endif + } + i->next = *first; + *first = add; +} + +/** Replace all matching VPs + * + * Walks over 'first', and replaces the first VP that matches 'replace'. + * + * @note Memory used by the VP being replaced will be freed. + * @note Will not work with unknown attributes. + * + * @param[in,out] first VP in linked list. Will search and replace in this list. + * @param[in] replace VP to replace. + */ +void fr_pair_replace(VALUE_PAIR **first, VALUE_PAIR *replace) +{ + VALUE_PAIR *i, *next; + VALUE_PAIR **prev = first; + + VERIFY_VP(replace); + + if (*first == NULL) { + *first = replace; + return; + } + + /* + * Not an empty list, so find item if it is there, and + * replace it. Note, we always replace the first one, and + * we ignore any others that might exist. + */ + for(i = *first; i; i = next) { + VERIFY_VP(i); + next = i->next; + + /* + * Found the first attribute, replace it, + * and return. + */ + if ((i->da == replace->da) && (!i->da->flags.has_tag || TAG_EQ(replace->tag, i->tag))) { + *prev = replace; + + /* + * Should really assert that replace->next == NULL + */ + replace->next = next; + talloc_free(i); + return; + } + + /* + * Point to where the attribute should go. + */ + prev = &i->next; + } + + /* + * If we got here, we didn't find anything to replace, so + * stopped at the last item, which we just append to. + */ + *prev = replace; +} + +int8_t fr_pair_cmp_by_da_tag(void const *a, void const *b) +{ + VALUE_PAIR const *my_a = a; + VALUE_PAIR const *my_b = b; + + VERIFY_VP(my_a); + VERIFY_VP(my_b); + + uint8_t cmp; + + cmp = fr_pointer_cmp(my_a->da, my_b->da); + if (cmp != 0) return cmp; + + if (my_a->tag < my_b->tag) return -1; + + if (my_a->tag > my_b->tag) return 1; + + return 0; +} + +static void fr_pair_list_sort_split(VALUE_PAIR *source, VALUE_PAIR **front, VALUE_PAIR **back) +{ + VALUE_PAIR *fast; + VALUE_PAIR *slow; + + /* + * Stopping condition - no more elements left to split + */ + if (!source || !source->next) { + *front = source; + *back = NULL; + + return; + } + + /* + * Fast advances twice as fast as slow, so when it gets to the end, + * slow will point to the middle of the linked list. + */ + slow = source; + fast = source->next; + + while (fast) { + fast = fast->next; + if (fast) { + slow = slow->next; + fast = fast->next; + } + } + + *front = source; + *back = slow->next; + slow->next = NULL; +} + +static VALUE_PAIR *fr_pair_list_sort_merge(VALUE_PAIR *a, VALUE_PAIR *b, fr_cmp_t cmp) +{ + VALUE_PAIR *result = NULL; + + if (!a) return b; + if (!b) return a; + + /* + * Compare the DICT_ATTRs and tags + */ + if (cmp(a, b) <= 0) { + result = a; + result->next = fr_pair_list_sort_merge(a->next, b, cmp); + } else { + result = b; + result->next = fr_pair_list_sort_merge(a, b->next, cmp); + } + + return result; +} + +/** Sort a linked list of VALUE_PAIRs using merge sort + * + * @param[in,out] vps List of VALUE_PAIRs to sort. + * @param[in] cmp to sort with + */ +void fr_pair_list_sort(VALUE_PAIR **vps, fr_cmp_t cmp) +{ + VALUE_PAIR *head = *vps; + VALUE_PAIR *a; + VALUE_PAIR *b; + + /* + * If there's 0-1 elements it must already be sorted. + */ + if (!head || !head->next) { + return; + } + + fr_pair_list_sort_split(head, &a, &b); /* Split into sublists */ + fr_pair_list_sort(&a, cmp); /* Traverse left */ + fr_pair_list_sort(&b, cmp); /* Traverse right */ + + /* + * merge the two sorted lists together + */ + *vps = fr_pair_list_sort_merge(a, b, cmp); +} + +/** Write an error to the library errorbuff detailing the mismatch + * + * Retrieve output with fr_strerror(); + * + * @todo add thread specific talloc contexts. + * + * @param ctx a hack until we have thread specific talloc contexts. + * @param failed pair of attributes which didn't match. + */ +void fr_pair_validate_debug(TALLOC_CTX *ctx, VALUE_PAIR const *failed[2]) +{ + VALUE_PAIR const *filter = failed[0]; + VALUE_PAIR const *list = failed[1]; + + char *value, *str; + + (void) fr_strerror(); /* Clear any existing messages */ + + if (!fr_assert(!(!filter && !list))) return; + + if (!list) { + if (!filter) return; + fr_strerror_printf("Attribute \"%s\" not found in list", filter->da->name); + return; + } + + if (!filter || (filter->da != list->da)) { + fr_strerror_printf("Attribute \"%s\" not found in filter", list->da->name); + return; + } + + if (!TAG_EQ(filter->tag, list->tag)) { + fr_strerror_printf("Attribute \"%s\" tag \"%i\" didn't match filter tag \"%i\"", + list->da->name, list->tag, filter->tag); + return; + } + + + value = vp_aprints_value(ctx, list, '"'); + str = vp_aprints(ctx, filter, '"'); + + fr_strerror_printf("Attribute value \"%s\" didn't match filter: %s", value, str); + + talloc_free(str); + talloc_free(value); + + return; +} + +/** Uses fr_pair_cmp to verify all VALUE_PAIRs in list match the filter defined by check + * + * @note will sort both filter and list in place. + * + * @param failed pointer to an array to write the pointers of the filter/list attributes that didn't match. + * May be NULL. + * @param filter attributes to check list against. + * @param list attributes, probably a request or reply + */ +bool fr_pair_validate(VALUE_PAIR const *failed[2], VALUE_PAIR *filter, VALUE_PAIR *list) +{ + vp_cursor_t filter_cursor; + vp_cursor_t list_cursor; + + VALUE_PAIR *check, *match; + + if (!filter && !list) { + return true; + } + + /* + * This allows us to verify the sets of validate and reply are equal + * i.e. we have a validate rule which matches every reply attribute. + * + * @todo this should be removed one we have sets and lists + */ + fr_pair_list_sort(&filter, fr_pair_cmp_by_da_tag); + fr_pair_list_sort(&list, fr_pair_cmp_by_da_tag); + + check = fr_cursor_init(&filter_cursor, &filter); + match = fr_cursor_init(&list_cursor, &list); + while (match || check) { + /* + * Lists are of different lengths + */ + if (!match || !check) goto mismatch; + + /* + * The lists are sorted, so if the first + * attributes aren't of the same type, then we're + * done. + */ + if (!ATTRIBUTE_EQ(check, match)) goto mismatch; + + /* + * They're of the same type, but don't have the + * same values. This is a problem. + * + * Note that the RFCs say that for attributes of + * the same type, order is important. + */ + if (fr_pair_cmp(check, match) != 1) goto mismatch; + + check = fr_cursor_next(&filter_cursor); + match = fr_cursor_next(&list_cursor); + } + + return true; + +mismatch: + if (failed) { + failed[0] = check; + failed[1] = match; + } + return false; +} + +/** Uses fr_pair_cmp to verify all VALUE_PAIRs in list match the filter defined by check + * + * @note will sort both filter and list in place. + * + * @param failed pointer to an array to write the pointers of the filter/list attributes that didn't match. + * May be NULL. + * @param filter attributes to check list against. + * @param list attributes, probably a request or reply + */ +bool fr_pair_validate_relaxed(VALUE_PAIR const *failed[2], VALUE_PAIR *filter, VALUE_PAIR *list) +{ + vp_cursor_t filter_cursor; + vp_cursor_t list_cursor; + + VALUE_PAIR *check, *last_check = NULL, *match = NULL; + + if (!filter && !list) { + return true; + } + + /* + * This allows us to verify the sets of validate and reply are equal + * i.e. we have a validate rule which matches every reply attribute. + * + * @todo this should be removed one we have sets and lists + */ + fr_pair_list_sort(&filter, fr_pair_cmp_by_da_tag); + fr_pair_list_sort(&list, fr_pair_cmp_by_da_tag); + + fr_cursor_init(&list_cursor, &list); + for (check = fr_cursor_init(&filter_cursor, &filter); + check; + check = fr_cursor_next(&filter_cursor)) { + /* + * Were processing check attributes of a new type. + */ + if (!ATTRIBUTE_EQ(last_check, check)) { + /* + * Record the start of the matching attributes in the pair list + * For every other operator we require the match to be present + */ + match = fr_cursor_next_by_da(&list_cursor, check->da, check->tag); + if (!match) { + if (check->op == T_OP_CMP_FALSE) continue; + goto mismatch; + } + + fr_cursor_init(&list_cursor, &match); + last_check = check; + } + + /* + * Now iterate over all attributes of the same type. + */ + for (match = fr_cursor_first(&list_cursor); + ATTRIBUTE_EQ(match, check); + match = fr_cursor_next(&list_cursor)) { + /* + * This attribute passed the filter + */ + if (!fr_pair_cmp(check, match)) goto mismatch; + } + } + + return true; + +mismatch: + if (failed) { + failed[0] = check; + failed[1] = match; + } + return false; +} + +/** Copy a single valuepair + * + * Allocate a new valuepair and copy the da from the old vp. + * + * @param[in] ctx for talloc + * @param[in] vp to copy. + * @return a copy of the input VP or NULL on error. + */ +VALUE_PAIR *fr_pair_copy(TALLOC_CTX *ctx, VALUE_PAIR const *vp) +{ + VALUE_PAIR *n; + + if (!vp) return NULL; + + VERIFY_VP(vp); + + n = fr_pair_afrom_da(ctx, vp->da); + if (!n) return NULL; + + memcpy(n, vp, sizeof(*n)); + + /* + * If the DA is unknown, steal "n" to "ctx". This does + * nothing for "n", but will also copy the unknown "da". + */ + if (n->da->flags.is_unknown) { + fr_pair_steal(ctx, n); + } + + n->next = NULL; + + /* + * If it's an xlat, copy the raw string and return early, + * so we don't pre-expand or otherwise mangle the VALUE_PAIR. + */ + if (vp->type == VT_XLAT) { + n->value.xlat = talloc_typed_strdup(n, n->value.xlat); + return n; + } + + switch (vp->da->type) { + case PW_TYPE_OCTETS: + n->vp_octets = NULL; /* else fr_pair_value_memcpy will free vp's value */ + fr_pair_value_memcpy(n, vp->vp_octets, n->vp_length); + break; + + case PW_TYPE_STRING: + n->vp_strvalue = NULL; /* else pairstrnpy will free vp's value */ + fr_pair_value_bstrncpy(n, vp->vp_strvalue, n->vp_length); + break; + + default: + break; + } + + return n; +} + +/** Copy a pairlist. + * + * Copy all pairs from 'from' regardless of tag, attribute or vendor. + * + * @param[in] ctx for new VALUE_PAIRs to be allocated in. + * @param[in] from whence to copy VALUE_PAIRs. + * @return the head of the new VALUE_PAIR list or NULL on error. + */ +VALUE_PAIR *fr_pair_list_copy(TALLOC_CTX *ctx, VALUE_PAIR *from) +{ + vp_cursor_t src, dst; + + VALUE_PAIR *out = NULL, *vp; + + fr_cursor_init(&dst, &out); + for (vp = fr_cursor_init(&src, &from); + vp; + vp = fr_cursor_next(&src)) { + VERIFY_VP(vp); + vp = fr_pair_copy(ctx, vp); + if (!vp) { + fr_pair_list_free(&out); + return NULL; + } + fr_cursor_insert(&dst, vp); /* fr_pair_list_copy sets next pointer to NULL */ + } + + return out; +} + +/** Copy matching pairs + * + * Copy pairs of a matching attribute number, vendor number and tag from the + * the input list to a new list, and returns the head of this list. + * + * @param[in] ctx for talloc + * @param[in] from whence to copy VALUE_PAIRs. + * @param[in] attr to match. If attribute PW_VENDOR_SPECIFIC and vendor 0, + * will match (and therefore copy) only VSAs. + * If attribute 0 and vendor 0 will match (and therefore copy) all + * attributes. + * @param[in] vendor to match. + * @param[in] tag to match, TAG_ANY matches any tag, TAG_NONE matches tagless VPs. + * @return the head of the new VALUE_PAIR list or NULL on error. + */ +VALUE_PAIR *fr_pair_list_copy_by_num(TALLOC_CTX *ctx, VALUE_PAIR *from, + unsigned int attr, unsigned int vendor, int8_t tag) +{ + vp_cursor_t src, dst; + + VALUE_PAIR *out = NULL, *vp; + + fr_cursor_init(&dst, &out); + for (vp = fr_cursor_init(&src, &from); + vp; + vp = fr_cursor_next(&src)) { + VERIFY_VP(vp); + + if (vp->da->flags.has_tag && !TAG_EQ(tag, vp->tag)) { + continue; + } + + /* + * Attr/vendor of 0 means "move them all". + * It's better than "fr_pair_copy(foo,bar);bar=NULL" + */ + if ((attr == 0) && (vendor == 0)) { + goto do_copy; + } + + /* + * vendor=0, attr = PW_VENDOR_SPECIFIC means + * "match any vendor attribute". + */ + if ((vendor == 0) && (attr == PW_VENDOR_SPECIFIC)) { + /* + * It's a VSA: copy it over. + */ + if (vp->da->vendor != 0) goto do_copy; + + /* + * It's Vendor-Specific: copy it over. + */ + if (vp->da->attr == attr) goto do_copy; + + /* + * It's not a VSA: ignore it. + */ + continue; + } + + if ((vp->da->attr != attr) || (vp->da->vendor != vendor)) { + continue; + } + + do_copy: + vp = fr_pair_copy(ctx, vp); + if (!vp) { + fr_pair_list_free(&out); + return NULL; + } + fr_cursor_insert(&dst, vp); + } + + return out; +} + +/** Steal one VP + * + * @param[in] ctx to move VALUE_PAIR into + * @param[in] vp VALUE_PAIR to move into the new context. + */ +void fr_pair_steal(TALLOC_CTX *ctx, VALUE_PAIR *vp) +{ + (void) talloc_steal(ctx, vp); + + /* + * The DA may be unknown. If we're stealing the VPs to a + * different context, copy the unknown DA. We use the VP + * as a context here instead of "ctx", so that when the + * VP is freed, so is the DA. + * + * Since we have no introspection into OTHER VPs using + * the same DA, we can't have multiple VPs use the same + * DA. So we might as well tie it to this VP. + */ + if (vp->da->flags.is_unknown) { + DICT_ATTR *da; + char *p; + size_t size; + + size = talloc_get_size(vp->da); + + p = talloc_zero_array(vp, char, size); + da = (DICT_ATTR *) p; + talloc_set_type(p, DICT_ATTR); + memcpy(da, vp->da, size); + vp->da = da; + } +} + +/** Move pairs from source list to destination list respecting operator + * + * @note This function does some additional magic that's probably not needed + * in most places. Consider using radius_pairmove in server code. + * + * @note fr_pair_list_free should be called on the head of the source list to free + * unmoved attributes (if they're no longer needed). + * + * @note Does not respect tags when matching. + * + * @param[in] ctx for talloc + * @param[in,out] to destination list. + * @param[in,out] from source list. + * @param[in] op operator for move. + * + * @see radius_pairmove + */ +void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op) +{ + VALUE_PAIR *i, *found; + VALUE_PAIR *head_new, **tail_new; + VALUE_PAIR *head_prepend; + VALUE_PAIR **tail_from; + + if (!to || !from || !*from) return; + + /* + * We're editing the "to" list while we're adding new + * attributes to it. We don't want the new attributes to + * be edited, so we create an intermediate list to hold + * them during the editing process. + */ + head_new = NULL; + tail_new = &head_new; + + /* + * Any attributes that are requested to be prepended + * are added to a temporary list here + */ + head_prepend = NULL; + + /* + * We're looping over the "from" list, moving some + * attributes out, but leaving others in place. + */ + tail_from = from; + while ((i = *tail_from) != NULL) { + VALUE_PAIR *j; + + VERIFY_VP(i); + + /* + * We never move Fall-Through. + */ + if (!i->da->vendor && i->da->attr == PW_FALL_THROUGH) { + tail_from = &(i->next); + continue; + } + + /* + * Unlike previous versions, we treat all other + * attributes as normal. i.e. there's no special + * treatment for passwords or Hint. + */ + + switch (i->op) { + /* + * Anything else are operators which + * shouldn't occur. We ignore them, and + * leave them in place. + */ + default: + tail_from = &(i->next); + continue; + + /* + * Add it to the "to" list, but only if + * it doesn't already exist. + */ + case T_OP_EQ: + found = fr_pair_find_by_da(*to, i->da, TAG_ANY); + if (!found) goto do_add; + + tail_from = &(i->next); + continue; + + /* + * Add it to the "to" list, and delete any attribute + * of the same vendor/attr which already exists. + */ + case T_OP_SET: + found = fr_pair_find_by_da(*to, i->da, TAG_ANY); + if (!found) goto do_add; + + /* + * Do NOT call fr_pair_delete_by_num() here, + * due to issues with re-writing + * "request->username". + * + * Everybody calls fr_pair_move, and + * expects it to work. We can't + * update request->username here, + * so instead we over-write the + * vp that it's pointing to. + */ + switch (found->da->type) { + default: + j = found->next; + memcpy(found, i, sizeof(*found)); + found->next = j; + break; + + case PW_TYPE_OCTETS: + fr_pair_value_memsteal(found, i->vp_octets); + i->vp_octets = NULL; + break; + + case PW_TYPE_STRING: + fr_pair_value_strsteal(found, i->vp_strvalue); + i->vp_strvalue = NULL; + found->tag = i->tag; + break; + } + + /* + * Delete *all* of the attributes + * of the same number. + */ + fr_pair_delete_by_num(&found->next, + found->da->attr, + found->da->vendor, TAG_ANY); + + /* + * Remove this attribute from the + * "from" list. + */ + *tail_from = i->next; + i->next = NULL; + fr_pair_list_free(&i); + continue; + + /* + * Move it from the old list and add it + * to the new list. + */ + case T_OP_ADD: + do_add: + *tail_from = i->next; + i->next = NULL; + *tail_new = i; + fr_pair_steal(ctx, i); + tail_new = &(i->next); + continue; + case T_OP_PREPEND: + i->next = head_prepend; + head_prepend = i; + fr_pair_steal(ctx, i); + continue; + } + } /* loop over the "from" list. */ + + /* + * If the op parameter was prepend, add the "new" list + * attributes first as those whose individual operator + * is prepend should be prepended to the resulting list + */ + if (op == T_OP_PREPEND) { + fr_pair_prepend(to, head_new); + } + + /* + * If there are any items in the prepend list prepend + * it to the "to" list + */ + fr_pair_prepend(to, head_prepend); + + /* + * If the op parameter was not prepend, take the "new" + * list, and append it to the "to" list. + */ + if (op != T_OP_PREPEND) { + fr_pair_add(to, head_new); + } +} + +/** Move matching pairs between VALUE_PAIR lists + * + * Move pairs of a matching attribute number, vendor number and tag from the + * the input list to the output list. + * + * @note fr_pair_list_free should be called on the head of the old list to free unmoved + attributes (if they're no longer needed). + * + * @param[in] ctx for talloc + * @param[in,out] to destination list. + * @param[in,out] from source list. + * @param[in] attr to match. If attribute PW_VENDOR_SPECIFIC and vendor 0, + * will match (and therefore copy) only VSAs. + * If attribute 0 and vendor 0 will match (and therefore copy) all + * attributes. + * @param[in] vendor to match. + * @param[in] tag to match, TAG_ANY matches any tag, TAG_NONE matches tagless VPs. + * @param[in] move if set to "true", VPs are moved. If set to "false", VPs are copied, and the old one deleted. + */ +static void fr_pair_list_move_by_num_internal(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, + unsigned int attr, unsigned int vendor, int8_t tag, + bool move) +{ + VALUE_PAIR *to_tail, *i, *next, *this; + VALUE_PAIR *iprev = NULL; + + /* + * Find the last pair in the "to" list and put it in "to_tail". + * + * @todo: replace the "if" with "VALUE_PAIR **tail" + */ + if (*to != NULL) { + to_tail = *to; + for(i = *to; i; i = i->next) { + VERIFY_VP(i); + to_tail = i; + } + } else + to_tail = NULL; + + /* + * Attr/vendor of 0 means "move them all". + * It's better than "fr_pair_add(foo,bar);bar=NULL" + */ + if ((vendor == 0) && (attr == 0)) { + if (*to) { + to_tail->next = *from; + } else { + *to = *from; + } + + for (i = *from; i; i = i->next) { + fr_pair_steal(ctx, i); + } + + *from = NULL; + return; + } + + for(i = *from; i; i = next) { + VERIFY_VP(i); + next = i->next; + + if (i->da->flags.has_tag && !TAG_EQ(tag, i->tag)) { + iprev = i; + continue; + } + + /* + * vendor=0, attr = PW_VENDOR_SPECIFIC means + * "match any vendor attribute". + */ + if ((vendor == 0) && (attr == PW_VENDOR_SPECIFIC)) { + /* + * It's a VSA: move it over. + */ + if (i->da->vendor != 0) goto move; + + /* + * It's Vendor-Specific: move it over. + */ + if (i->da->attr == attr) goto move; + + /* + * It's not a VSA: ignore it. + */ + iprev = i; + continue; + } + + /* + * If it isn't an exact match, ignore it. + */ + if (!((i->da->vendor == vendor) && (i->da->attr == attr))) { + iprev = i; + continue; + } + + move: + /* + * Remove the attribute from the "from" list. + */ + if (iprev) + iprev->next = next; + else + *from = next; + + if (move) { + this = i; + } else { + this = fr_pair_copy(ctx, i); + } + + /* + * Add the attribute to the "to" list. + */ + if (to_tail) + to_tail->next = this; + else + *to = this; + to_tail = this; + this->next = NULL; + + if (move) { + fr_pair_steal(ctx, i); + } else { + talloc_free(i); + } + } +} + + +/** Move matching pairs between VALUE_PAIR lists + * + * Move pairs of a matching attribute number, vendor number and tag from the + * the input list to the output list. + * + * @note pairs which are moved have their parent changed to ctx. + * + * @note fr_pair_list_free should be called on the head of the old list to free unmoved + attributes (if they're no longer needed). + * + * @param[in] ctx for talloc + * @param[in,out] to destination list. + * @param[in,out] from source list. + * @param[in] attr to match. If attribute PW_VENDOR_SPECIFIC and vendor 0, + * will match (and therefore copy) only VSAs. + * If attribute 0 and vendor 0 will match (and therefore copy) all + * attributes. + * @param[in] vendor to match. + * @param[in] tag to match, TAG_ANY matches any tag, TAG_NONE matches tagless VPs. + */ +void fr_pair_list_move_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, + unsigned int attr, unsigned int vendor, int8_t tag) +{ + fr_pair_list_move_by_num_internal(ctx, to, from, attr, vendor, tag, true); +} + + +/** Copy / delete matching pairs between VALUE_PAIR lists + * + * Move pairs of a matching attribute number, vendor number and tag from the + * the input list to the output list. Like fr_pair_list_move_by_num(), but + * instead does copy / delete. + * + * @note The pair is NOT reparented. It is copied and deleted. + * + * @note fr_pair_list_free should be called on the head of the old list to free unmoved + attributes (if they're no longer needed). + * + * @param[in] ctx for talloc + * @param[in,out] to destination list. + * @param[in,out] from source list. + * @param[in] attr to match. If attribute PW_VENDOR_SPECIFIC and vendor 0, + * will match (and therefore copy) only VSAs. + * If attribute 0 and vendor 0 will match (and therefore copy) all + * attributes. + * @param[in] vendor to match. + * @param[in] tag to match, TAG_ANY matches any tag, TAG_NONE matches tagless VPs. + */ +void fr_pair_list_mcopy_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, + unsigned int attr, unsigned int vendor, int8_t tag) +{ + fr_pair_list_move_by_num_internal(ctx, to, from, attr, vendor, tag, false); +} + + +/** Convert string value to native attribute value + * + * @param vp to assign value to. + * @param value string to convert. Binary safe for variable length values if len is provided. + * @param inlen may be < 0 in which case strlen(len) is used to determine length, else inline + * should be the length of the string or sub string to parse. + * @return 0 on success -1 on error. + */ +int fr_pair_value_from_str(VALUE_PAIR *vp, char const *value, size_t inlen) +{ + ssize_t ret; + PW_TYPE type; + VERIFY_VP(vp); + + if (!value) return -1; + + type = vp->da->type; + + /* + * We presume that the input data is from a double quoted + * string, and needs escaping + */ + ret = value_data_from_str(vp, &vp->data, &type, vp->da, value, inlen, '"'); + if (ret < 0) return -1; + + /* + * If we parsed to a different type than the DA associated with + * the VALUE_PAIR we now need to fixup the DA. + */ + if (type != vp->da->type) { + DICT_ATTR const *da; + + da = dict_attrbytype(vp->da->attr, vp->da->vendor, type); + if (!da) { + fr_strerror_printf("Cannot find %s variant of attribute \"%s\"", + fr_int2str(dict_attr_types, type, "<INVALID>"), vp->da->name); + return -1; + } + vp->da = da; + } + + vp->vp_length = ret; + vp->type = VT_DATA; + + VERIFY_VP(vp); + + return 0; +} + +/** Use simple heuristics to create an VALUE_PAIR from an unknown address string + * + * If a DICT_ATTR is not provided for the address type, parsing will fail with + * and error. + * + * @param ctx to allocate VP in. + * @param value IPv4/IPv6 address/prefix string. + * @param ipv4 dictionary attribute to use for an IPv4 address. + * @param ipv6 dictionary attribute to use for an IPv6 address. + * @param ipv4_prefix dictionary attribute to use for an IPv4 prefix. + * @param ipv6_prefix dictionary attribute to use for an IPv6 prefix. + * @return NULL on error, or new VALUE_PAIR. + */ +VALUE_PAIR *fr_pair_afrom_ip_str(TALLOC_CTX *ctx, char const *value, DICT_ATTR *ipv4, DICT_ATTR *ipv6, + DICT_ATTR *ipv4_prefix, DICT_ATTR *ipv6_prefix) +{ + VALUE_PAIR *vp; + DICT_ATTR *da = NULL; + + if (!fr_assert(ipv4 || ipv6 || ipv4_prefix || ipv6_prefix)) { + return NULL; + } + + /* No point in repeating the work of fr_pair_value_from_str */ + if (strchr(value, ':')) { + if (strchr(value, '/')) { + da = ipv6_prefix; + goto finish; + } + + da = ipv6; + goto finish; + } + + if (strchr(value, '/')) { + da = ipv4_prefix; + goto finish; + } + + if (ipv4) { + da = ipv4; + goto finish; + } + + fr_strerror_printf("Invalid IP value specified, allowed types are %s%s%s%s", + ipv4 ? "ipaddr " : "", ipv6 ? "ipv6addr " : "", + ipv4_prefix ? "ipv4prefix " : "", ipv6_prefix ? "ipv6prefix" : ""); + +finish: + vp = fr_pair_afrom_da(ctx, da); + if (!vp) return NULL; + if (fr_pair_value_from_str(vp, value, -1) < 0) { + talloc_free(vp); + return NULL; + } + + return vp; +} + + +/** Create a valuepair from an ASCII attribute and value + * + * Where the attribute name is in the form: + * - Attr-%d + * - Attr-%d.%d.%d... + * - Vendor-%d-Attr-%d + * - VendorName-Attr-%d + * + * @param ctx for talloc + * @param attribute name to parse. + * @param value to parse (must be a hex string). + * @param op to assign to new valuepair. + * @return new valuepair or NULL on error. + */ +static VALUE_PAIR *fr_pair_make_unknown(TALLOC_CTX *ctx, + char const *attribute, char const *value, + FR_TOKEN op) +{ + VALUE_PAIR *vp, *vp2; + DICT_ATTR const *da; + + uint8_t *data; + size_t size; + ssize_t len; + + vp = fr_pair_alloc(ctx); + if (!vp) return NULL; + + vp->da = dict_unknown_afrom_str(vp, attribute); + if (!vp->da) { + talloc_free(vp); + return NULL; + } + + /* + * No value. Nothing more to do. + */ + if (!value) return vp; + + /* + * Unknown attributes MUST be of type 'octets' + */ + if (strncasecmp(value, "0x", 2) != 0) { + fr_strerror_printf("Unknown attribute \"%s\" requires a hex " + "string, not \"%s\"", attribute, value); + talloc_free(vp); + return NULL; + } + + /* + * Convert the hex data to binary. + */ + size = strlen(value + 2); + + vp->vp_length = size >> 1; + vp->vp_octets = data = talloc_array(vp, uint8_t, vp->vp_length); + vp->type = VT_DATA; + vp->op = (op == 0) ? T_OP_EQ : op; + + if (fr_hex2bin(data, vp->vp_length, value + 2, size) != vp->vp_length) { + fr_strerror_printf("Invalid hex string"); + talloc_free(vp); + return NULL; + } + + /* + * It's still unknown, return it as-is. + */ + da = dict_attrbyvalue(vp->da->attr, vp->da->vendor); + if (!da) return vp; + + /* + * It MIGHT be known. See if we can decode the raw data + * into a valid attribute. + */ + len = data2vp(ctx, NULL, NULL, NULL, da, + vp->vp_octets, vp->vp_length, vp->vp_length, + &vp2); + if (len <= 0) return vp; + + /* + * It's still unknown. Return the original VP. + */ + if (vp2->da->flags.is_unknown) { + fr_pair_list_free(&vp2); + return vp; + } + + /* + * Didn't parse all of it. Return the "unknown" one. + * + * FIXME: it COULD have parsed 2 attributes and + * then not the third, so returning 2 "knowns" + * and 1 "unknown" is likely preferable. + */ + if ((size_t) len < vp->vp_length) { + fr_pair_list_free(&vp2); + return vp; + } + + fr_pair_list_free(&vp); + return vp2; +} + + +/** Create a VALUE_PAIR from ASCII strings + * + * Converts an attribute string identifier (with an optional tag qualifier) + * and value string into a VALUE_PAIR. + * + * The string value is parsed according to the type of VALUE_PAIR being created. + * + * @param[in] ctx for talloc + * @param[in] vps list where the attribute will be added (optional) + * @param[in] attribute name. + * @param[in] value attribute value (may be NULL if value will be set later). + * @param[in] op to assign to new VALUE_PAIR. + * @return a new VALUE_PAIR. + */ +VALUE_PAIR *fr_pair_make(TALLOC_CTX *ctx, VALUE_PAIR **vps, + char const *attribute, char const *value, FR_TOKEN op) +{ + DICT_ATTR const *da; + VALUE_PAIR *vp; + char *tc, *ts; + int8_t tag; + bool found_tag; + char buffer[256]; + char const *attrname = attribute; + + /* + * Check for tags in 'Attribute:Tag' format. + */ + found_tag = false; + tag = TAG_ANY; + + ts = strrchr(attribute, ':'); + if (ts && !ts[1]) { + fr_strerror_printf("Invalid tag for attribute %s", attribute); + return NULL; + } + + if (ts && ts[1]) { + strlcpy(buffer, attribute, sizeof(buffer)); + attrname = buffer; + ts = strrchr(attrname, ':'); + if (!ts) return NULL; + + /* Colon found with something behind it */ + if (ts[1] == '*' && ts[2] == 0) { + /* Wildcard tag for check items */ + tag = TAG_ANY; + *ts = '\0'; + } else if ((ts[1] >= '0') && (ts[1] <= '9')) { + /* It's not a wild card tag */ + tag = strtol(ts + 1, &tc, 0); + if (tc && !*tc && TAG_VALID_ZERO(tag)) + *ts = '\0'; + else tag = TAG_ANY; + } else { + fr_strerror_printf("Invalid tag for attribute %s", attribute); + return NULL; + } + found_tag = true; + } + + /* + * It's not found in the dictionary, so we use + * another method to create the attribute. + */ + da = dict_attrbyname(attrname); + if (!da) { + vp = fr_pair_make_unknown(ctx, attrname, value, op); + if (vp && vps) fr_pair_add(vps, vp); + return vp; + } + + /* Check for a tag in the 'Merit' format of: + * :Tag:Value. Print an error if we already found + * a tag in the Attribute. + */ + + if (value && (*value == ':' && da->flags.has_tag)) { + /* If we already found a tag, this is invalid */ + if(found_tag) { + fr_strerror_printf("Duplicate tag %s for attribute %s", + value, da->name); + DEBUG("Duplicate tag %s for attribute %s\n", + value, da->name); + return NULL; + } + /* Colon found and attribute allows a tag */ + if (value[1] == '*' && value[2] == ':') { + /* Wildcard tag for check items */ + tag = TAG_ANY; + value += 3; + } else { + /* Real tag */ + tag = strtol(value + 1, &tc, 0); + if (tc && *tc==':' && TAG_VALID_ZERO(tag)) + value = tc + 1; + else tag = 0; + } + } + + vp = fr_pair_afrom_da(ctx, da); + if (!vp) return NULL; + vp->op = (op == 0) ? T_OP_EQ : op; + vp->tag = tag; + + switch (vp->op) { + case T_OP_CMP_TRUE: + case T_OP_CMP_FALSE: + vp->vp_strvalue = NULL; + vp->vp_length = 0; + value = NULL; /* ignore it! */ + break; + + /* + * Regular expression comparison of integer attributes + * does a STRING comparison of the names of their + * integer attributes. + */ + case T_OP_REG_EQ: /* =~ */ + case T_OP_REG_NE: /* !~ */ + { +#ifndef HAVE_REGEX + fr_strerror_printf("Regular expressions are not supported"); + return NULL; +#else + ssize_t slen; + regex_t *preg; + + /* + * Someone else will fill in the value. + */ + if (!value) break; + + talloc_free(vp); + + slen = regex_compile(ctx, &preg, value, strlen(value), false, false, false, true); + if (slen <= 0) { + fr_strerror_printf("Error at offset %zu compiling regex for %s: %s", -slen, + attribute, fr_strerror()); + return NULL; + } + talloc_free(preg); + + vp = fr_pair_make(ctx, NULL, attribute, NULL, op); + if (!vp) return NULL; + + if (fr_pair_mark_xlat(vp, value) < 0) { + talloc_free(vp); + return NULL; + } + + value = NULL; /* ignore it */ + break; +#endif + } + default: + break; + } + + /* + * We allow this for stupidity, but it's really a bad idea. + */ + if (vp->da->type == PW_TYPE_TLV) { + ssize_t len; + DICT_ATTR const *unknown; + VALUE_PAIR *head = NULL; + VALUE_PAIR **tail = &head; + + if (!value) { + talloc_free(vp); + return NULL; + } + + unknown = dict_unknown_afrom_fields(vp, vp->da->attr, vp->da->vendor); + if (!unknown) { + talloc_free(vp); + return NULL; + } + + vp->da = unknown; + + /* + * Parse it as an unknown type, i.e. octets. + */ + if (fr_pair_value_from_str(vp, value, -1) < 0) { + talloc_free(vp); + return NULL; + } + + /* + * It's badly formatted. Treat it as unknown. + */ + if (rad_tlv_ok(vp->vp_octets, vp->vp_length, 1, 1) < 0) { + goto do_add; + } + + /* + * Decode the TLVs + */ + len = rad_data2vp_tlvs(ctx, NULL, NULL, NULL, da, vp->vp_octets, + vp->vp_length, tail); + if (len < 0) { + goto do_add; + } + + talloc_free(vp); + vp = head; + goto do_add; + } + + /* + * FIXME: if (strcasecmp(attribute, vp->da->name) != 0) + * then the user MAY have typed in the attribute name + * as Vendor-%d-Attr-%d, and the value MAY be octets. + * + * We probably want to fix fr_pair_value_from_str to accept + * octets as values for any attribute. + */ + if (value && (fr_pair_value_from_str(vp, value, -1) < 0)) { + talloc_free(vp); + return NULL; + } + +do_add: + if (vps) fr_pair_add(vps, vp); + return vp; +} + +/** Mark a valuepair for xlat expansion + * + * Copies xlat source (unprocessed) string to valuepair value, + * and sets value type. + * + * @param vp to mark for expansion. + * @param value to expand. + * @return 0 if marking succeeded or -1 if vp already had a value, or OOM. + */ +int fr_pair_mark_xlat(VALUE_PAIR *vp, char const *value) +{ + char *raw; + + /* + * valuepair should not already have a value. + */ + if (vp->type != VT_NONE) { + fr_strerror_printf("Pair already has a value"); + return -1; + } + + raw = talloc_typed_strdup(vp, value); + if (!raw) { + fr_strerror_printf("Out of memory"); + return -1; + } + + vp->type = VT_XLAT; + vp->value.xlat = raw; + vp->vp_length = 0; + + return 0; +} + + +/** Read a single valuepair from a buffer, and advance the pointer + * + * Returns T_EOL if end of line was encountered. + * + * @param[in,out] ptr to read from and update. + * @param[out] raw The struct to write the raw VALUE_PAIR to. + * @return the last token read. + */ +FR_TOKEN fr_pair_raw_from_str(char const **ptr, VALUE_PAIR_RAW *raw) +{ + char const *p; + char *q; + FR_TOKEN ret = T_INVALID, next, quote; + char buf[8]; + + if (!ptr || !*ptr || !raw) { + fr_strerror_printf("Invalid arguments"); + return T_INVALID; + } + + /* + * Skip leading spaces + */ + p = *ptr; + while ((*p == ' ') || (*p == '\t')) p++; + + if (!*p) { + fr_strerror_printf("No token read where we expected " + "an attribute name"); + return T_INVALID; + } + + if (*p == '#') return T_HASH; + + /* + * Try to get the attribute name. + */ + q = raw->l_opand; + *q = '\0'; + while (*p) { + uint8_t const *t = (uint8_t const *) p; + + if (q >= (raw->l_opand + sizeof(raw->l_opand))) { + too_long: + fr_strerror_printf("Attribute name too long"); + return T_INVALID; + } + + /* + * This is arguably easier than trying to figure + * out which operators come after the attribute + * name. Yes, our "lexer" is bad. + */ + if (!dict_attr_allowed_chars[(unsigned int) *t]) { + break; + } + + /* + * Attribute:=value is NOT + * + * Attribute: + * = + * value + */ + if ((*p == ':') && (!isdigit((uint8_t) p[1]))) { + break; + } + + *(q++) = *(p++); + } + + /* + * Haven't found any valid characters in the name. + */ + if (!*raw->l_opand) { + fr_strerror_printf("Invalid attribute name"); + return T_INVALID; + } + + /* + * Look for tag (:#). This is different from :=, which + * is an operator. + */ + if ((*p == ':') && (isdigit((uint8_t) p[1]))) { + if (q >= (raw->l_opand + sizeof(raw->l_opand))) { + goto too_long; + } + *(q++) = *(p++); + + while (isdigit((uint8_t) *p)) { + if (q >= (raw->l_opand + sizeof(raw->l_opand))) { + goto too_long; + } + *(q++) = *(p++); + } + } + + *q = '\0'; + *ptr = p; + + /* Now we should have an operator here. */ + raw->op = gettoken(ptr, buf, sizeof(buf), false); + if (raw->op < T_EQSTART || raw->op > T_EQEND) { + fr_strerror_printf("Expecting operator"); + + return T_INVALID; + } + + /* + * Read value. Note that empty string values are allowed + */ + quote = gettoken(ptr, raw->r_opand, sizeof(raw->r_opand), false); + if (quote == T_EOL) { + fr_strerror_printf("Failed to get value"); + + return T_INVALID; + } + + /* + * Peek at the next token. Must be T_EOL, T_COMMA, or T_HASH + */ + p = *ptr; + + next = gettoken(&p, buf, sizeof(buf), false); + switch (next) { + case T_HASH: + next = T_EOL; + break; + + case T_EOL: + break; + + case T_COMMA: + *ptr = p; + break; + + default: + fr_strerror_printf("Expected end of line or comma"); + return T_INVALID; + } + ret = next; + + switch (quote) { + /* + * Perhaps do xlat's + */ + case T_DOUBLE_QUOTED_STRING: + /* + * Only report as double quoted if it contained valid + * a valid xlat expansion. + */ + p = strchr(raw->r_opand, '%'); + if (p && (p[1] == '{')) { + raw->quote = quote; + } else { + raw->quote = T_SINGLE_QUOTED_STRING; + } + + break; + + case T_SINGLE_QUOTED_STRING: + case T_BACK_QUOTED_STRING: + case T_BARE_WORD: + raw->quote = quote; + break; + + default: + fr_strerror_printf("Failed to find expected value on right hand side in %s", raw->l_opand); + return T_INVALID; + } + + return ret; +} + +/** Read one line of attribute/value pairs into a list. + * + * The line may specify multiple attributes separated by commas. + * + * @note If the function returns T_INVALID, an error has occurred and + * @note the valuepair list should probably be freed. + * + * @param ctx for talloc + * @param buffer to read valuepairs from. + * @param list where the parsed VALUE_PAIRs will be appended. + * @return the last token parsed, or T_INVALID + */ +FR_TOKEN fr_pair_list_afrom_str(TALLOC_CTX *ctx, char const *buffer, VALUE_PAIR **list) +{ + VALUE_PAIR *vp, *head, **tail; + char const *p; + FR_TOKEN last_token = T_INVALID; + VALUE_PAIR_RAW raw; + + /* + * We allow an empty line. + */ + if (buffer[0] == 0) { + return T_EOL; + } + + head = NULL; + tail = &head; + + p = buffer; + do { + raw.l_opand[0] = '\0'; + raw.r_opand[0] = '\0'; + + last_token = fr_pair_raw_from_str(&p, &raw); + + /* + * JUST a hash. Don't try to create a VP. + * Let the caller determine if an empty list is OK. + */ + if (last_token == T_HASH) { + last_token = T_EOL; + break; + } + if (last_token == T_INVALID) break; + + if (raw.quote == T_DOUBLE_QUOTED_STRING) { + vp = fr_pair_make(ctx, NULL, raw.l_opand, NULL, raw.op); + if (!vp) { + last_token = T_INVALID; + break; + } + if (fr_pair_mark_xlat(vp, raw.r_opand) < 0) { + talloc_free(vp); + last_token = T_INVALID; + break; + } + } else { + vp = fr_pair_make(ctx, NULL, raw.l_opand, raw.r_opand, raw.op); + if (!vp) { + last_token = T_INVALID; + break; + } + } + + *tail = vp; + tail = &((*tail)->next); + } while (*p && (last_token == T_COMMA)); + + if (last_token == T_INVALID) { + fr_pair_list_free(&head); + } else { + fr_pair_add(list, head); + } + + /* + * And return the last token which we read. + */ + return last_token; +} + +/* + * Read valuepairs from the fp up to End-Of-File. + */ +int fr_pair_list_afrom_file(TALLOC_CTX *ctx, VALUE_PAIR **out, FILE *fp, bool *pfiledone) +{ + char buf[8192]; + FR_TOKEN last_token = T_EOL; + + vp_cursor_t cursor; + + VALUE_PAIR *vp = NULL; + fr_cursor_init(&cursor, out); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + /* + * If we get a '\n' by itself, we assume that's + * the end of that VP + */ + if (buf[0] == '\n') { + if (vp) { + *pfiledone = false; + return 0; + } + continue; + } + + /* + * Comments get ignored + */ + if (buf[0] == '#') continue; + + /* + * Read all of the attributes on the current line. + */ + vp = NULL; + last_token = fr_pair_list_afrom_str(ctx, buf, &vp); + if (!vp) { + if (last_token != T_EOL) goto error; + break; + } + + fr_cursor_merge(&cursor, vp); + buf[0] = '\0'; + } + *pfiledone = true; + + return 0; + +error: + *pfiledone = false; + vp = fr_cursor_first(&cursor); + if (vp) fr_pair_list_free(&vp); + + return -1; +} + +/** Compare two pairs, using the operator from "a" + * + * i.e. given two attributes, it does: + * + * (b->data) (a->operator) (a->data) + * + * e.g. "foo" != "bar" + * + * @param[in] a the first attribute + * @param[in] b the second attribute + * @return 1 if true, 0 if false, -1 on error. + */ +int fr_pair_cmp(VALUE_PAIR *a, VALUE_PAIR *b) +{ + if (!a) return -1; + + VERIFY_VP(a); + if (b) VERIFY_VP(b); + + switch (a->op) { + case T_OP_CMP_TRUE: + return (b != NULL); + + case T_OP_CMP_FALSE: + return (b == NULL); + + /* + * a is a regex, compile it, print b to a string, + * and then do string comparisons. + */ + case T_OP_REG_EQ: + case T_OP_REG_NE: +#ifndef HAVE_REGEX + return -1; +#else + if (!b) return false; + + { + ssize_t slen; + regex_t *preg; + char *value; + + if (!fr_assert(a->da->type == PW_TYPE_STRING)) return -1; + + slen = regex_compile(NULL, &preg, a->value.xlat, talloc_array_length(a->value.xlat) - 1, false, false, false, true); + if (slen <= 0) { + fr_strerror_printf("Error at offset %zu compiling regex for %s: %s", + -slen, a->da->name, fr_strerror()); + return -1; + } + value = vp_aprints_value(NULL, b, '\0'); + if (!value) { + talloc_free(preg); + return -1; + } + + /* + * Don't care about substring matches, oh well... + */ + slen = regex_exec(preg, value, talloc_array_length(value) - 1, NULL, NULL); + talloc_free(preg); + talloc_free(value); + + if (slen < 0) return -1; + if (a->op == T_OP_REG_EQ) return (int)slen; + return !slen; + } +#endif + + default: /* we're OK */ + if (!b) return false; + break; + } + + return fr_pair_cmp_op(a->op, b, a); +} + +/** Determine equality of two lists + * + * This is useful for comparing lists of attributes inserted into a binary tree. + * + * @param a first list of VALUE_PAIRs. + * @param b second list of VALUE_PAIRs. + * @return -1 if a < b, 0 if the two lists are equal, 1 if a > b, -2 on error. + */ +int fr_pair_list_cmp(VALUE_PAIR *a, VALUE_PAIR *b) +{ + vp_cursor_t a_cursor, b_cursor; + VALUE_PAIR *a_p, *b_p; + int ret; + + for (a_p = fr_cursor_init(&a_cursor, &a), b_p = fr_cursor_init(&b_cursor, &b); + a_p && b_p; + a_p = fr_cursor_next(&a_cursor), b_p = fr_cursor_next(&b_cursor)) { + /* Same VP, no point doing expensive checks */ + if (a_p == b_p) { + continue; + } + + if (a_p->da < b_p->da) { + return -1; + } + if (a_p->da > b_p->da) { + return 1; + } + + if (a_p->tag < b_p->tag) { + return -1; + } + if (a_p->tag > b_p->tag) { + return 1; + } + + ret = value_data_cmp(a_p->da->type, &a_p->data, a_p->vp_length, + b_p->da->type, &b_p->data, b_p->vp_length); + if (ret != 0) { + fr_assert(ret >= -1); /* Comparison error */ + return ret; + } + } + + if (!a_p && !b_p) { + return 0; + } + + if (!a_p) { + return -1; + } + + /* if(!b_p) */ + return 1; +} + +/** Set the type of the VALUE_PAIR value buffer to match it's DICT_ATTR + * + * @param vp to fixup. + */ +static void fr_pair_value_set_type(VALUE_PAIR *vp) +{ + if (!vp->data.ptr) return; + + switch (vp->da->type) { + case PW_TYPE_OCTETS: + talloc_set_type(vp->data.ptr, uint8_t); + return; + + case PW_TYPE_STRING: + talloc_set_type(vp->data.ptr, char); + return; + + default: + return; + } +} + +/** Copy data into an "octets" data type. + * + * @param[in,out] vp to update + * @param[in] src data to copy + * @param[in] size of the data, may be 0 in which case previous value will be freed. + */ +void fr_pair_value_memcpy(VALUE_PAIR *vp, uint8_t const *src, size_t size) +{ + uint8_t *p = NULL, *q; + + VERIFY_VP(vp); + + if (size > 0) { + p = talloc_memdup(vp, src, size); + if (!p) return; + talloc_set_type(p, uint8_t); + } + + memcpy(&q, &vp->vp_octets, sizeof(q)); + TALLOC_FREE(q); + + vp->vp_octets = p; + vp->vp_length = size; + + if (size > 0) fr_pair_value_set_type(vp); +} + +/** Reparent an allocated octet buffer to a VALUE_PAIR + * + * @param[in,out] vp to update + * @param[in] src buffer to steal. + */ +void fr_pair_value_memsteal(VALUE_PAIR *vp, uint8_t const *src) +{ + uint8_t *q; + + VERIFY_VP(vp); + + memcpy(&q, &vp->vp_octets, sizeof(q)); + talloc_free(q); + + vp->vp_octets = talloc_steal(vp, src); + vp->type = VT_DATA; + vp->vp_length = talloc_array_length(vp->vp_strvalue); + fr_pair_value_set_type(vp); +} + +/** Reparent an allocated char buffer to a VALUE_PAIR + * + * @param[in,out] vp to update + * @param[in] src buffer to steal. + */ +void fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src) +{ + uint8_t *q; + + VERIFY_VP(vp); + + memcpy(&q, &vp->vp_octets, sizeof(q)); + talloc_free(q); + + vp->vp_strvalue = talloc_steal(vp, src); + vp->type = VT_DATA; + vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1; + fr_pair_value_set_type(vp); +} + +/** Copy data into an "string" data type. + * + * @param[in,out] vp to update + * @param[in] src data to copy + */ +void fr_pair_value_strcpy(VALUE_PAIR *vp, char const *src) +{ + char *p, *q; + + VERIFY_VP(vp); + + p = talloc_strdup(vp, src); + + if (!p) return; + + memcpy(&q, &vp->vp_strvalue, sizeof(q)); + talloc_free(q); + + vp->vp_strvalue = p; + vp->type = VT_DATA; + vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1; + fr_pair_value_set_type(vp); +} + +/** Copy data into an "string" data type. + * + * @note unlike the original strncpy, this function does not stop + * if it finds \0 bytes embedded in the string. + * + * @param[in,out] vp to update. + * @param[in] src data to copy. + * @param[in] len of data to copy. + */ +void fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const *src, size_t len) +{ + char *p, *q; + + VERIFY_VP(vp); + + if (!src) return; + + p = talloc_array(vp, char, len + 1); + if (!p) return; + + memcpy(p, src, len); /* embdedded \0 safe */ + p[len] = '\0'; + + memcpy(&q, &vp->vp_strvalue, sizeof(q)); + talloc_free(q); + + vp->vp_strvalue = p; + vp->type = VT_DATA; + vp->vp_length = len; + fr_pair_value_set_type(vp); +} + +/** Print data into an "string" data type. + * + * @param[in,out] vp to update + * @param[in] fmt the format string + */ +void fr_pair_value_sprintf(VALUE_PAIR *vp, char const *fmt, ...) +{ + va_list ap; + char *p, *q; + + VERIFY_VP(vp); + + va_start(ap, fmt); + p = talloc_vasprintf(vp, fmt, ap); + va_end(ap); + + if (!p) return; + + memcpy(&q, &vp->vp_strvalue, sizeof(q)); + talloc_free(q); + + vp->vp_strvalue = p; + vp->type = VT_DATA; + + vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1; + fr_pair_value_set_type(vp); +} + +#ifdef WITH_VERIFY_PTR +/* + * Verify a VALUE_PAIR + */ +inline void fr_pair_verify(char const *file, int line, VALUE_PAIR const *vp) +{ + if (!vp) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR pointer was NULL", file, line); + fr_assert(0); + fr_exit_now(1); + } + + (void) talloc_get_type_abort(vp, VALUE_PAIR); + + if (!vp->da) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR da pointer was NULL", file, line); + fr_assert(0); + fr_exit_now(1); + } + + if (vp->data.ptr) switch (vp->da->type) { + case PW_TYPE_OCTETS: + { + size_t len; + TALLOC_CTX *parent; + + if (!talloc_get_type(vp->data.ptr, uint8_t)) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be " + "uint8_t but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr)); + (void) talloc_get_type_abort(vp->data.ptr, uint8_t); + } + + len = talloc_array_length(vp->vp_octets); + if (vp->vp_length > len) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than " + "uint8_t data buffer length %zu\n", file, line, vp->da->name, vp->vp_length, len); + fr_assert(0); + fr_exit_now(1); + } + + parent = talloc_parent(vp->data.ptr); + if (parent != vp) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer is not " + "parented by VALUE_PAIR %p, instead parented by %p (%s)\n", + file, line, vp->da->name, + vp, parent, parent ? talloc_get_name(parent) : "NULL"); + fr_assert(0); + fr_exit_now(1); + } + } + break; + + case PW_TYPE_STRING: + { + size_t len; + TALLOC_CTX *parent; + + if (!talloc_get_type(vp->data.ptr, char)) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be " + "char but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr)); + (void) talloc_get_type_abort(vp->data.ptr, char); + } + + len = (talloc_array_length(vp->vp_strvalue) - 1); + if (vp->vp_length > len) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than " + "char buffer length %zu\n", file, line, vp->da->name, vp->vp_length, len); + fr_assert(0); + fr_exit_now(1); + } + + if (vp->vp_strvalue[vp->vp_length] != '\0') { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer not \\0 " + "terminated\n", file, line, vp->da->name); + fr_assert(0); + fr_exit_now(1); + } + + parent = talloc_parent(vp->data.ptr); + if (parent != vp) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer is not " + "parented by VALUE_PAIR %p, instead parented by %p (%s)\n", + file, line, vp->da->name, + vp, parent, parent ? talloc_get_name(parent) : "NULL"); + fr_assert(0); + fr_exit_now(1); + } + } + break; + + default: + break; + } + + if (vp->da->flags.is_unknown) { + (void) talloc_get_type_abort(vp->da, DICT_ATTR); + } else { + DICT_ATTR const *da; + + /* + * Attribute may be present with multiple names + */ + da = dict_attrbyname(vp->da->name); + if (!da) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR attribute %p \"%s\" (%s) " + "not found in global dictionary", + file, line, vp->da, vp->da->name, + fr_int2str(dict_attr_types, vp->da->type, "<INVALID>")); + fr_assert(0); + fr_exit_now(1); + } + + if (da->type == PW_TYPE_COMBO_IP_ADDR) { + da = dict_attrbytype(vp->da->attr, vp->da->vendor, vp->da->type); + if (!da) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR attribute %p \"%s\" " + "variant (%s) not found in global dictionary", + file, line, vp->da, vp->da->name, + fr_int2str(dict_attr_types, vp->da->type, "<INVALID>")); + fr_assert(0); + fr_exit_now(1); + } + } + + + if (da != vp->da) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR " + "dictionary pointer %p \"%s\" (%s) " + "and global dictionary pointer %p \"%s\" (%s) differ", + file, line, vp->da, vp->da->name, + fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"), + da, da->name, fr_int2str(dict_attr_types, da->type, "<INVALID>")); + fr_assert(0); + fr_exit_now(1); + } + } +} + +/* + * Verify a pair list + */ +void fr_pair_list_verify(char const *file, int line, TALLOC_CTX *expected, VALUE_PAIR *vps, char const *name) +{ + vp_cursor_t cursor; + VALUE_PAIR *vp; + TALLOC_CTX *parent; + + for (vp = fr_cursor_init(&cursor, &vps); + vp; + vp = fr_cursor_next(&cursor)) { + VERIFY_VP(vp); + + parent = talloc_parent(vp); + if (expected && (parent != expected)) { + FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: Expected VALUE_PAIR \"%s\" to be parented " + "by %p (%s) name %s, instead parented by %p (%s)\n", + file, line, vp->da->name, + expected, talloc_get_name(expected), name, + parent, parent ? talloc_get_name(parent) : "NULL"); + + fr_log_talloc_report(expected); + if (parent) fr_log_talloc_report(parent); + + fr_assert(0); + fr_exit_now(1); + } + + } +} +#endif diff --git a/src/lib/pcap.c b/src/lib/pcap.c new file mode 100644 index 0000000..987a8ea --- /dev/null +++ b/src/lib/pcap.c @@ -0,0 +1,474 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 of the + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file pcap.c + * @brief Wrappers around libpcap functions + * + * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org> + * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org> + */ +#ifdef HAVE_LIBPCAP + +#include <sys/ioctl.h> +#include <freeradius-devel/pcap.h> + +const FR_NAME_NUMBER pcap_types[] = { + { "interface", PCAP_INTERFACE_IN }, + { "file", PCAP_FILE_IN }, + { "stdio", PCAP_STDIO_IN }, + { "interface", PCAP_INTERFACE_OUT }, + { "file", PCAP_FILE_OUT }, + { "stdio", PCAP_STDIO_OUT }, + + { NULL, 0} +}; + +/** Talloc destructor to free pcap resources associated with a handle. + * + * @param pcap to free. + * @return 0 + */ +static int _free_pcap(fr_pcap_t *pcap) { + switch (pcap->type) { + case PCAP_INTERFACE_IN: + case PCAP_INTERFACE_OUT: + case PCAP_FILE_IN: + case PCAP_STDIO_IN: + if (pcap->handle) { + pcap_close(pcap->handle); + + if (pcap->fd > 0) { + close(pcap->fd); + } + } + + break; + + case PCAP_FILE_OUT: + case PCAP_STDIO_OUT: + if (pcap->dumper) { + pcap_dump_flush(pcap->dumper); + pcap_dump_close(pcap->dumper); + } + + break; + case PCAP_INVALID: + break; + } + + return 0; +} + +/** Get data link from pcap_if_t + * + * libpcap requires an open pcap handle to get data_link type + * unfortunately when we're trying to find useful interfaces + * this is too late. + * + * @param errbuff Error message. + * @param dev to get link layer for. + * @return datalink layer or -1 on failure. + */ +int fr_pcap_if_link_layer(char *errbuff, pcap_if_t *dev) +{ + pcap_t *pcap; + int data_link; + + pcap = pcap_open_live(dev->name, 0, 0, 0, errbuff); + if (!pcap) return -1; + + data_link = pcap_datalink(pcap); + pcap_close(pcap); + + return data_link; +} + +/** Initialise a pcap handle abstraction + * + * @param ctx talloc TALLOC_CTX to allocate handle in. + * @param name of interface or file to open. + * @param type of handle to initialise. + * @return new handle or NULL on error. + */ +fr_pcap_t *fr_pcap_init(TALLOC_CTX *ctx, char const *name, fr_pcap_type_t type) +{ + fr_pcap_t *this = talloc_zero(ctx, fr_pcap_t); + if (!this) { + return NULL; + } + + talloc_set_destructor(this, _free_pcap); + this->name = talloc_typed_strdup(this, name); + this->type = type; + this->link_layer = -1; + + return this; +} + +/** Open a PCAP handle abstraction + * + * This opens interfaces for capture or injection, or files/streams for reading/writing. + * @param pcap created with fr_pcap_init. + * @return 0 on success, -1 on error. + */ +int fr_pcap_open(fr_pcap_t *pcap) +{ + switch (pcap->type) { + case PCAP_INTERFACE_OUT: + case PCAP_INTERFACE_IN: + { +#if defined(HAVE_PCAP_CREATE) && defined(HAVE_PCAP_ACTIVATE) + pcap->handle = pcap_create(pcap->name, pcap->errbuf); + if (!pcap->handle) { + fr_strerror_printf("%s", pcap->errbuf); + return -1; + } + if (pcap_set_snaplen(pcap->handle, SNAPLEN) != 0) { + create_error: + fr_strerror_printf("%s", pcap_geterr(pcap->handle)); + pcap_close(pcap->handle); + pcap->handle = NULL; + return -1; + } + if (pcap_set_timeout(pcap->handle, PCAP_NONBLOCK_TIMEOUT) != 0) { + goto create_error; + } + if (pcap_set_promisc(pcap->handle, pcap->promiscuous) != 0) { + goto create_error; + } + + if (pcap_set_buffer_size(pcap->handle, SNAPLEN * + (pcap->buffer_pkts ? pcap->buffer_pkts : PCAP_BUFFER_DEFAULT)) != 0) { + goto create_error; + } + if (pcap_activate(pcap->handle) != 0) { + goto create_error; + } +#else + /* + * Alternative functions for libpcap < 1.0 + */ + pcap->handle = pcap_open_live(pcap->name, SNAPLEN, pcap->promiscuous, PCAP_NONBLOCK_TIMEOUT, + pcap->errbuf); + if (!pcap->handle) { + fr_strerror_printf("%s", pcap->errbuf); + return -1; + } +#endif + /* + * Despite accepting an errbuff, pcap_setnonblock doesn't seem to write + * error message there in newer versions. + */ + if (pcap_setnonblock(pcap->handle, true, pcap->errbuf) != 0) { + fr_strerror_printf("%s", *pcap->errbuf != '\0' ? + pcap->errbuf : pcap_geterr(pcap->handle)); + pcap_close(pcap->handle); + pcap->handle = NULL; + return -1; + } + + pcap->fd = pcap_get_selectable_fd(pcap->handle); + pcap->link_layer = pcap_datalink(pcap->handle); +#ifndef __linux__ + { + int value = 1; + if (ioctl(pcap->fd, BIOCIMMEDIATE, &value) < 0) { + fr_strerror_printf("Failed setting BIOCIMMEDIATE: %s", fr_syserror(errno)); + } + } +#endif + } + break; + + case PCAP_FILE_IN: + pcap->handle = pcap_open_offline(pcap->name, pcap->errbuf); + if (!pcap->handle) { + fr_strerror_printf("%s", pcap->errbuf); + + return -1; + } + pcap->fd = pcap_get_selectable_fd(pcap->handle); + pcap->link_layer = pcap_datalink(pcap->handle); + break; + + case PCAP_FILE_OUT: + if (pcap->link_layer < 0) { + pcap->link_layer = DLT_EN10MB; + } + pcap->handle = pcap_open_dead(pcap->link_layer, SNAPLEN); + if (!pcap->handle) { + fr_strerror_printf("Unknown error occurred opening dead PCAP handle"); + + return -1; + } + pcap->dumper = pcap_dump_open(pcap->handle, pcap->name); + if (!pcap->dumper) { + fr_strerror_printf("%s", pcap_geterr(pcap->handle)); + + return -1; + } + break; + +#ifdef HAVE_PCAP_FOPEN_OFFLINE + case PCAP_STDIO_IN: + pcap->handle = pcap_fopen_offline(stdin, pcap->errbuf); + if (!pcap->handle) { + fr_strerror_printf("%s", pcap->errbuf); + + return -1; + } + pcap->fd = pcap_get_selectable_fd(pcap->handle); + pcap->link_layer = pcap_datalink(pcap->handle); + break; +#else + case PCAP_STDIO_IN: + fr_strerror_printf("This version of libpcap does not support reading pcap data from streams"); + + return -1; +#endif +#ifdef HAVE_PCAP_DUMP_FOPEN + case PCAP_STDIO_OUT: + pcap->handle = pcap_open_dead(DLT_EN10MB, SNAPLEN); + pcap->dumper = pcap_dump_fopen(pcap->handle, stdout); + if (!pcap->dumper) { + fr_strerror_printf("%s", pcap_geterr(pcap->handle)); + + return -1; + } + break; +#else + case PCAP_STDIO_OUT: + fr_strerror_printf("This version of libpcap does not support writing pcap data to streams"); + + return -1; +#endif + case PCAP_INVALID: + default: + fr_assert(0); + fr_strerror_printf("Bad handle type (%i)", pcap->type); + return -1; + } + + return 0; +} + +/** Apply capture filter to an interface + * + * @param pcap handle to apply filter to. + * @param expression PCAP expression to use as a filter. + * @return 0 on success, 1 can't apply to interface, -1 on error. + */ +int fr_pcap_apply_filter(fr_pcap_t *pcap, char const *expression) +{ + bpf_u_int32 mask = 0; /* Our netmask */ + bpf_u_int32 net = 0; /* Our IP */ + struct bpf_program fp; + + /* + * nflog devices are in the set of devices selected by default. + * Unfortunately there's a bug in all released version of libpcap (as of 2/1/2014) + * which triggers an abort if pcap_setfilter is called on an nflog interface. + * + * See here: + * https://github.com/the-tcpdump-group/libpcap/commit/676cf8a61ed240d0a86d471ef419f45ba35dba80 + */ +#ifdef DLT_NFLOG + if (pcap->link_layer == DLT_NFLOG) { + fr_strerror_printf("NFLOG link-layer type filtering not implemented"); + + return 1; + } +#endif + + if (pcap->type == PCAP_INTERFACE_IN) { + if (pcap_lookupnet(pcap->name, &net, &mask, pcap->errbuf) < 0) { + fr_strerror_printf("Failed getting IP for interface \"%s\", using defaults: %s", + pcap->name, pcap->errbuf); + } + } + + if (pcap_compile(pcap->handle, &fp, expression, 0, net) < 0) { + fr_strerror_printf("%s", pcap_geterr(pcap->handle)); + + return -1; + } + + if (pcap_setfilter(pcap->handle, &fp) < 0) { + fr_strerror_printf("%s", pcap_geterr(pcap->handle)); + + return -1; + } + + return 0; +} + +char *fr_pcap_device_names(TALLOC_CTX *ctx, fr_pcap_t *pcap, char c) +{ + fr_pcap_t *pcap_p; + char *buff, *p; + size_t len = 0, left = 0, wrote; + + if (!pcap) { + goto null; + } + + for (pcap_p = pcap; + pcap_p; + pcap_p = pcap_p->next) { + len += talloc_array_length(pcap_p->name); // Talloc array length includes the \0 + } + + if (!len) { + null: + return talloc_zero_array(ctx, char, 1); + } + + left = len + 1; + buff = p = talloc_zero_array(ctx, char, left); + for (pcap_p = pcap; + pcap_p; + pcap_p = pcap_p->next) { + wrote = snprintf(p, left, "%s%c", pcap_p->name, c); + left -= wrote; + p += wrote; + } + buff[len - 1] = '\0'; + + return buff; +} + +/** Check whether fr_pcap_link_layer_offset can process a link_layer + * + * @param link_layer to check. + * @return true if supported, else false. + */ +bool fr_pcap_link_layer_supported(int link_layer) +{ + switch (link_layer) { + case DLT_EN10MB: + case DLT_RAW: + case DLT_NULL: + case DLT_LOOP: +#ifdef DLT_LINUX_SLL + case DLT_LINUX_SLL: +#endif + case DLT_PFLOG: + return true; + + default: + return false; + } +} + +/** Returns the length of the link layer header + * + * Libpcap does not include a decoding function to skip the L2 header, but it does + * at least inform us of the type. + * + * Unfortunately some headers are of variable length (like ethernet), so additional + * decoding logic is required. + * + * @note No header data is returned, this is only meant to be used to determine how + * data to consume before attempting to parse the IP header. + * + * @param data start of packet data. + * @param len caplen. + * @param link_layer value returned from pcap_linktype. + * @return the length of the header, or -1 on error. + */ +ssize_t fr_pcap_link_layer_offset(uint8_t const *data, size_t len, int link_layer) +{ + uint8_t const *p = data; + + switch (link_layer) { + case DLT_RAW: + break; + + case DLT_NULL: + case DLT_LOOP: + p += 4; + if (((size_t)(p - data)) > len) { + ood: + fr_strerror_printf("Out of data, needed %zu bytes, have %zu bytes", + (size_t)(p - data), len); + return -1; + } + break; + + case DLT_EN10MB: + { + uint16_t ether_type; /* Ethernet type */ + int i; + + p += 12; /* SRC/DST Mac-Addresses */ + if (((size_t)(p - data)) > len) { + goto ood; + } + + for (i = 0; i < 3; i++) { + ether_type = ntohs(*((uint16_t const *) p)); + switch (ether_type) { + /* + * There are a number of devices out there which + * double tag with 0x8100 *sigh* + */ + case 0x8100: /* CVLAN */ + case 0x9100: /* SVLAN */ + case 0x9200: /* SVLAN */ + case 0x9300: /* SVLAN */ + p += 4; + if (((size_t)(p - data)) > len) { + goto ood; + } + break; + + default: + p += 2; + if (((size_t)(p - data)) > len) { + goto ood; + } + goto done; + } + } + fr_strerror_printf("Exceeded maximum level of VLAN tag nesting (2)"); + return -1; + } + +#ifdef DLT_LINUX_SLL + case DLT_LINUX_SLL: + p += 16; + if (((size_t)(p - data)) > len) { + goto ood; + } + break; +#endif + + case DLT_PFLOG: + p += 28; + if (((size_t)(p - data)) > len) { + goto ood; + } + break; + + default: + fr_strerror_printf("Unsupported link layer type %i", link_layer); + } + +done: + return p - data; +} +#endif /* HAVE_LIBPCAP */ diff --git a/src/lib/print.c b/src/lib/print.c new file mode 100644 index 0000000..57455b6 --- /dev/null +++ b/src/lib/print.c @@ -0,0 +1,790 @@ +/* + * print.c Routines to print stuff. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#include <ctype.h> + +/** Checks for utf-8, taken from http://www.w3.org/International/questions/qa-forms-utf-8 + * + * @param str input string. + * @param inlen length of input string. May be -1 if str is \0 terminated. + */ +int fr_utf8_char(uint8_t const *str, ssize_t inlen) +{ + if (inlen == 0) return 0; + + if (inlen < 0) inlen = 4; /* longest char */ + + if (*str < 0x20) return 0; + + if (*str <= 0x7e) return 1; /* 1 */ + + if (*str <= 0xc1) return 0; + + if (inlen < 2) return 0; + + if ((str[0] >= 0xc2) && /* 2 */ + (str[0] <= 0xdf) && + (str[1] >= 0x80) && + (str[1] <= 0xbf)) { + return 2; + } + + if (inlen < 3) return 0; + + if ((str[0] == 0xe0) && /* 3 */ + (str[1] >= 0xa0) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf)) { + return 3; + } + + if ((str[0] >= 0xe1) && /* 4a */ + (str[0] <= 0xec) && + (str[1] >= 0x80) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf)) { + return 3; + } + + if ((str[0] >= 0xee) && /* 4b */ + (str[0] <= 0xef) && + (str[1] >= 0x80) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf)) { + return 3; + } + + if ((str[0] == 0xed) && /* 5 */ + (str[1] >= 0x80) && + (str[1] <= 0x9f) && + (str[2] >= 0x80) && + (str[2] <= 0xbf)) { + return 3; + } + + if (inlen < 4) return 0; + + if ((str[0] == 0xf0) && /* 6 */ + (str[1] >= 0x90) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf) && + (str[3] >= 0x80) && + (str[3] <= 0xbf)) { + return 4; + } + + if ((str[0] >= 0xf1) && /* 6 */ + (str[0] <= 0xf3) && + (str[1] >= 0x80) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf) && + (str[3] >= 0x80) && + (str[3] <= 0xbf)) { + return 4; + } + + + if ((str[0] == 0xf4) && /* 7 */ + (str[1] >= 0x80) && + (str[1] <= 0x8f) && + (str[2] >= 0x80) && + (str[2] <= 0xbf) && + (str[3] >= 0x80) && + (str[3] <= 0xbf)) { + return 4; + } + + /* + * Invalid UTF-8 Character + */ + return 0; +} + +/** Return a pointer to the first UTF8 char in a string. + * + * @param[out] chr_len Where to write the length of the multibyte char passed in chr (may be NULL). + * @param[in] str Haystack. + * @param[in] chr Multibyte needle. + * @return The position of chr in str or NULL if not found. + */ +char const *fr_utf8_strchr(int *chr_len, char const *str, char const *chr) +{ + int cchr; + + cchr = fr_utf8_char((uint8_t const *)chr, -1); + if (cchr == 0) cchr = 1; + if (chr_len) *chr_len = cchr; + + while (*str) { + int schr; + + schr = fr_utf8_char((uint8_t const *) str, -1); + if (schr == 0) schr = 1; + if (schr != cchr) goto next; + + if (memcmp(str, chr, schr) == 0) { + return (char const *) str; + } + next: + str += schr; + } + + return NULL; +} + +/** Escape any non printable or non-UTF8 characters in the input string + * + * @note Return value should be checked with is_truncated + * @note Will always \0 terminate unless outlen == 0. + * + * @param[in] in string to escape. + * @param[in] inlen length of string to escape (lets us deal with embedded NULs) + * @param[out] out where to write the escaped string. + * @param[out] outlen the length of the buffer pointed to by out. + * @param[in] quote the quotation character + * @return the number of bytes it WOULD HAVE written to the buffer, not including the trailing NUL + */ +size_t fr_prints(char *out, size_t outlen, char const *in, ssize_t inlen, char quote) +{ + uint8_t const *p = (uint8_t const *) in; + size_t utf8; + size_t used; + size_t freespace; + + /* No input, so no output... */ + if (!in) { + if (out && outlen) *out = '\0'; + return 0; + } + + /* Figure out the length of the input string */ + if (inlen < 0) inlen = strlen(in); + + /* + * No quotation character, just use memcpy, ensuring we + * don't overflow the output buffer. + */ + if (!quote) { + if (!out) return inlen; + + if ((size_t)inlen >= outlen) { + memcpy(out, in, outlen - 1); + out[outlen - 1] = '\0'; + } else { + memcpy(out, in, inlen); + out[inlen] = '\0'; + } + + return inlen; + } + + /* + * Check the output buffer and length. Zero both of them + * out if either are zero. + */ + freespace = outlen; + if (freespace == 0) out = NULL; + if (!out) freespace = 0; + + used = 0; + + while (inlen > 0) { + int sp = 0; + + /* + * Hack: never print trailing zero. + * Some clients send pings with an off-by-one + * length (confused with strings in C). + */ + if ((inlen == 1) && (*p == '\0')) { + inlen--; + break; + } + + /* + * Always escape the quotation character. + */ + if (*p == quote) { + sp = quote; + goto do_escape; + } + + /* + * Escape the backslash ONLY for single quoted strings. + */ + if (quote == '\'') { + if (*p == '\\') { + sp = '\\'; + } + goto do_escape; + } + + /* + * Try to convert 0x0a --> \r, etc. + * Backslashes get handled specially. + */ + switch (*p) { + case '\r': + sp = 'r'; + break; + + case '\n': + sp = 'n'; + break; + + case '\t': + sp = 't'; + break; + + case '\\': + sp = '\\'; + break; + + default: + sp = '\0'; + break; + } /* escape the character at *p */ + + do_escape: + if (sp) { + if ((freespace > 0) && (freespace <= 2)) { + if (out) out[used] = '\0'; + out = NULL; + freespace = 0; + + } else if (freespace > 2) { /* room for char AND trailing zero */ + if (out) { + out[used] = '\\'; + out[used + 1] = sp; + } + freespace -= 2; + } + + used += 2; + p++; + inlen--; + continue; + } + + /* + * All strings are UTF-8 clean. + */ + utf8 = fr_utf8_char(p, inlen); + + /* + * If we have an invalid UTF-8 character, it gets + * copied over as a 1-byte character for single + * quoted strings. Which means that the output + * isn't strictly UTF-8, but oh well... + * + * For double quoted strints, the invalid + * characters get escaped as octal encodings. + */ + if (utf8 == 0) { + if (quote == '\'') { + utf8 = 1; + + } else { + if ((freespace > 0) && (freespace <= 4)) { + if (out) out[used] = '\0'; + out = NULL; + freespace = 0; + + } else if (freespace > 4) { /* room for char AND trailing zero */ + if (out) snprintf(out + used, freespace, "\\%03o", *p); + freespace -= 4; + } + + used += 4; + p++; + inlen--; + continue; + } + } + + if ((freespace > 0) && (freespace <= utf8)) { + if (out) out[used] = '\0'; + out = NULL; + freespace = 0; + + } else if (freespace > utf8) { /* room for char AND trailing zero */ + if (out) memcpy(out + used, p, utf8); + freespace -= utf8; + } + + used += utf8; + p += utf8; + inlen -= utf8; + } + + /* + * Ensure that the output buffer is always zero terminated. + */ + if (out && freespace) out[used] = '\0'; + + return used; +} + +/** Find the length of the buffer required to fully escape a string with fr_prints + * + * Were assuming here that's it's cheaper to figure out the length and do one + * alloc than repeatedly expand the buffer when we find extra chars which need + * to be added. + * + * @param in string to calculate the escaped length for. + * @param inlen length of the input string, if < 0 strlen will be used to check the length. + * @param[in] quote the quotation character. + * @return the size of buffer required to hold the escaped string including the NUL byte. + */ +size_t fr_prints_len(char const *in, ssize_t inlen, char quote) +{ + return fr_prints(NULL, 0, in, inlen, quote) + 1; +} + +/** Escape string that may contain binary data, and write it to a new buffer + * + * This is useful in situations where we expect printable strings as input, + * but under some conditions may get binary data. A good example is libldap + * and the arrays of struct berval ldap_get_values_len returns. + * + * @param[in] ctx To allocate new buffer in. + * @param[in] in String to escape. + * @param[in] inlen Length of string. Should be >= 0 if the data may contain + * embedded \0s. Must be >= 0 if data may not be \0 terminated. + * If < 0 inlen will be calculated using strlen. + * @param[in] quote the quotation character. + * @return new buffer holding the escaped string. + */ +char *fr_aprints(TALLOC_CTX *ctx, char const *in, ssize_t inlen, char quote) +{ + size_t len, ret; + char *out; + + len = fr_prints_len(in, inlen, quote); + + out = talloc_array(ctx, char, len); + ret = fr_prints(out, len, in, inlen, quote); + + /* + * This is a fatal error, but fr_assert is the strongest + * assert we're allowed to use in library functions. + */ + if (!fr_assert(ret == (len - 1))) { + talloc_free(out); + return NULL; + } + + return out; +} + +/** Print the value of an attribute to a string + * + * @param[out] out Where to write the string. + * @param[in] outlen Size of outlen (must be at least 3 bytes). + * @param[in] vp to print. + * @param[in] quote Char to add before and after printed value, if 0 no char will be added, if < 0 raw string will be + * added. + * @return the length of data written to out, or a value >= outlen on truncation. + */ +size_t vp_prints_value(char *out, size_t outlen, VALUE_PAIR const *vp, char quote) +{ + VERIFY_VP(vp); + + if (vp->type == VT_XLAT) { + return snprintf(out, outlen, "%c%s%c", quote, vp->value.xlat, quote); + } + + return value_data_prints(out, outlen, vp->da->type, vp->da, &vp->data, vp->vp_length, quote); +} + +/** Print one attribute value to a string + * + * @param ctx to allocate string in. + * @param vp to print. + * @param[in] quote the quotation character + * @return a talloced buffer with the attribute operator and value. + */ +char *vp_aprints_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote) +{ + VERIFY_VP(vp); + + if (vp->type == VT_XLAT) { + return fr_aprints(ctx, vp->value.xlat, talloc_array_length(vp->value.xlat) - 1, quote); + } + + return value_data_aprints(ctx, vp->da->type, vp->da, &vp->data, vp->vp_length, quote); +} + +char *vp_aprints_type(TALLOC_CTX *ctx, PW_TYPE type) +{ + switch (type) { + case PW_TYPE_STRING : + return talloc_typed_strdup(ctx, "_"); + + case PW_TYPE_INTEGER64: + case PW_TYPE_SIGNED: + case PW_TYPE_BYTE: + case PW_TYPE_SHORT: + case PW_TYPE_INTEGER: + case PW_TYPE_DATE : + return talloc_typed_strdup(ctx, "0"); + + case PW_TYPE_IPV4_ADDR : + return talloc_typed_strdup(ctx, "?.?.?.?"); + + case PW_TYPE_IPV4_PREFIX: + return talloc_typed_strdup(ctx, "?.?.?.?/?"); + + case PW_TYPE_IPV6_ADDR: + return talloc_typed_strdup(ctx, "[:?:]"); + + case PW_TYPE_IPV6_PREFIX: + return talloc_typed_strdup(ctx, "[:?:]/?"); + + case PW_TYPE_OCTETS: + return talloc_typed_strdup(ctx, "??"); + + case PW_TYPE_ETHERNET: + return talloc_typed_strdup(ctx, "??:??:??:??:??:??:??:??"); + +#ifdef WITH_ASCEND_BINARY + case PW_TYPE_ABINARY: + return talloc_typed_strdup(ctx, "??"); +#endif + + default : + break; + } + + return talloc_typed_strdup(ctx, "<UNKNOWN-TYPE>"); +} + +/** Prints attribute enumv escaped suitably for use as JSON enumv + * + * Returns < 0 if the buffer may be (or have been) too small to write the encoded + * JSON value to. + * + * @param out Where to write the string. + * @param outlen Length of output buffer. + * @param vp to print. + * @param raw_value if true, the raw value is printed and not the enumerated attribute value + * @return the length of data written to out, or a value >= outlen on truncation. + */ +size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value) +{ + char const *q; + size_t len, freespace = outlen; + /* attempt to print raw_value when has_value is false, or raw_value is false, but only + if has_tag is also false */ + bool raw = (raw_value || !vp->da->flags.has_value) && !vp->da->flags.has_tag; + + if (raw) { + switch (vp->da->type) { + case PW_TYPE_INTEGER: + return snprintf(out, freespace, "%u", vp->vp_integer); + + case PW_TYPE_SHORT: + return snprintf(out, freespace, "%u", (unsigned int) vp->vp_short); + + case PW_TYPE_BYTE: + return snprintf(out, freespace, "%u", (unsigned int) vp->vp_byte); + + default: + break; + } + } + + /* Indicate truncation */ + if (freespace < 2) return outlen + 1; + *out++ = '"'; + freespace--; + + switch (vp->da->type) { + case PW_TYPE_STRING: + for (q = vp->vp_strvalue; q < vp->vp_strvalue + vp->vp_length; q++) { + /* Indicate truncation */ + if (freespace < 3) return outlen + 1; + + if (*q == '"') { + *out++ = '\\'; + *out++ = '"'; + freespace -= 2; + } else if (*q == '\\') { + *out++ = '\\'; + *out++ = '\\'; + freespace -= 2; + } else if (*q == '/') { + *out++ = '\\'; + *out++ = '/'; + freespace -= 2; + } else if (*q >= ' ') { + *out++ = *q; + freespace--; + } else { + *out++ = '\\'; + freespace--; + + switch (*q) { + case '\b': + *out++ = 'b'; + freespace--; + break; + + case '\f': + *out++ = 'f'; + freespace--; + break; + + case '\n': + *out++ = 'n'; + freespace--; + break; + + case '\r': + *out++ = 'r'; + freespace--; + break; + + case '\t': + *out++ = 't'; + freespace--; + break; + default: + len = snprintf(out, freespace, "u%04X", (uint8_t) *q); + if (is_truncated(len, freespace)) return (outlen - freespace) + len; + out += len; + freespace -= len; + } + } + } + break; + + default: + len = vp_prints_value(out, freespace, vp, 0); + if (is_truncated(len, freespace)) return (outlen - freespace) + len; + out += len; + freespace -= len; + break; + } + + /* Indicate truncation */ + if (freespace < 2) return outlen + 1; + *out++ = '"'; + freespace--; + *out = '\0'; // We don't increment out, because the nul byte should not be included in the length + + return outlen - freespace; +} + +/* + * This is a hack, and has to be kept in sync with tokens.h + */ +static char const *vp_tokens[] = { + "?", /* T_INVALID */ + "EOL", /* T_EOL */ + "{", + "}", + "(", + ")", + ",", + ";", + "++", + "+=", + "-=", + ":=", + "=", + "!=", + ">=", + ">", + "<=", + "<", + "=~", + "!~", + "=*", + "!*", + "==", + "#", + "<BARE-WORD>", + "<\"STRING\">", + "<'STRING'>", + "<`STRING`>" +}; + +/** Print one attribute and value to a string + * + * Print a VALUE_PAIR in the format: +@verbatim + <attribute_name>[:tag] <op> <value> +@endverbatim + * to a string. + * + * @param out Where to write the string. + * @param outlen Length of output buffer. + * @param vp to print. + * @return the length of data written to out, or a value >= outlen on truncation. + */ +size_t vp_prints(char *out, size_t outlen, VALUE_PAIR const *vp) +{ + char const *token = NULL; + size_t len, freespace = outlen; + + if (!out) return 0; + + *out = '\0'; + if (!vp || !vp->da) return 0; + + VERIFY_VP(vp); + + if ((vp->op > T_INVALID) && (vp->op < T_TOKEN_LAST)) { + token = vp_tokens[vp->op]; + } else { + token = "<INVALID-TOKEN>"; + } + + if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { + len = snprintf(out, freespace, "%s:%d %s ", vp->da->name, vp->tag, token); + } else { + len = snprintf(out, freespace, "%s %s ", vp->da->name, token); + } + + if (is_truncated(len, freespace)) return len; + out += len; + freespace -= len; + + len = vp_prints_value(out, freespace, vp, '"'); + if (is_truncated(len, freespace)) return (outlen - freespace) + len; + freespace -= len; + + return (outlen - freespace); +} + +/** Print one attribute and value to FP + * + * Complete string with '\\t' and '\\n' is written to buffer before printing to + * avoid issues when running with multiple threads. + * + * @param fp to output to. + * @param vp to print. + */ +void vp_print(FILE *fp, VALUE_PAIR const *vp) +{ + char buf[1024]; + char *p = buf; + size_t len; + + VERIFY_VP(vp); + + *p++ = '\t'; + len = vp_prints(p, sizeof(buf) - 1, vp); + if (!len) { + return; + } + p += len; + + /* + * Deal with truncation gracefully + */ + if (((size_t) (p - buf)) >= (sizeof(buf) - 2)) { + p = buf + (sizeof(buf) - 2); + } + + *p++ = '\n'; + *p = '\0'; + + fputs(buf, fp); +} + + +/** Print a list of attributes and enumv + * + * @param fp to output to. + * @param const_vp to print. + */ +void vp_printlist(FILE *fp, VALUE_PAIR const *const_vp) +{ + VALUE_PAIR *vp; + vp_cursor_t cursor; + + memcpy(&vp, &const_vp, sizeof(vp)); /* const work-arounds */ + + for (vp = fr_cursor_init(&cursor, &vp); vp; vp = fr_cursor_next(&cursor)) { + vp_print(fp, vp); + } +} + +/** Print one attribute and value to a string + * + * Print a VALUE_PAIR in the format: +@verbatim + <attribute_name>[:tag] <op> <value> +@endverbatim + * to a string. + * + * @param ctx to allocate string in. + * @param vp to print. + * @param[in] quote the quotation character + * @return a talloced buffer with the attribute operator and value. + */ +char *vp_aprints(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote) +{ + char const *token = NULL; + char *str, *value; + + if (!vp || !vp->da) return 0; + + VERIFY_VP(vp); + + if ((vp->op > T_INVALID) && (vp->op < T_TOKEN_LAST)) { + token = vp_tokens[vp->op]; + } else { + token = "<INVALID-TOKEN>"; + } + + value = vp_aprints_value(ctx, vp, quote); + + if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { + if (quote && (vp->da->type == PW_TYPE_STRING)) { + str = talloc_asprintf(ctx, "%s:%d %s %c%s%c", vp->da->name, vp->tag, token, quote, value, quote); + } else { + str = talloc_asprintf(ctx, "%s:%d %s %s", vp->da->name, vp->tag, token, value); + } + } else { + if (quote && (vp->da->type == PW_TYPE_STRING)) { + str = talloc_asprintf(ctx, "%s %s %c%s%c", vp->da->name, token, quote, value, quote); + } else { + str = talloc_asprintf(ctx, "%s %s %s", vp->da->name, token, value); + } + } + + talloc_free(value); + + return str; +} diff --git a/src/lib/radius.c b/src/lib/radius.c new file mode 100644 index 0000000..b2de15b --- /dev/null +++ b/src/lib/radius.c @@ -0,0 +1,5354 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file radius.c + * @brief Functions to send/receive radius packets. + * + * @copyright 2000-2003,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#include <freeradius-devel/md5.h> +#include <freeradius-devel/rfc4849.h> + +#include <fcntl.h> +#include <ctype.h> + +#ifdef WITH_UDPFROMTO +#include <freeradius-devel/udpfromto.h> +#endif + +/* + * Some messages get printed out only in debugging mode. + */ +#define FR_DEBUG_STRERROR_PRINTF if (fr_debug_lvl) fr_strerror_printf + +#if 0 +#define VP_TRACE printf + +static void VP_HEXDUMP(char const *msg, uint8_t const *data, size_t len) +{ + size_t i; + + printf("--- %s ---\n", msg); + for (i = 0; i < len; i++) { + if ((i & 0x0f) == 0) printf("%04x: ", (unsigned int) i); + printf("%02x ", data[i]); + if ((i & 0x0f) == 0x0f) printf("\n"); + } + if ((len == 0x0f) || ((len & 0x0f) != 0x0f)) printf("\n"); + fflush(stdout); +} + +#else +#define VP_TRACE(_x, ...) +#define VP_HEXDUMP(_x, _y, _z) +#endif + + +/* + * The maximum number of attributes which we allow in an incoming + * request. If there are more attributes than this, the request + * is rejected. + * + * This helps to minimize the potential for a DoS, when an + * attacker spoofs Access-Request packets, which don't have a + * Message-Authenticator attribute. This means that the packet + * is unsigned, and the attacker can use resources on the server, + * even if the end request is rejected. + */ +uint32_t fr_max_attributes = 0; +FILE *fr_log_fp = NULL; + +typedef struct radius_packet_t { + uint8_t code; + uint8_t id; + uint8_t length[2]; + uint8_t vector[AUTH_VECTOR_LEN]; + uint8_t data[1]; +} radius_packet_t; + +static fr_randctx fr_rand_pool; /* across multiple calls */ +static int fr_rand_initialized = 0; +#ifndef WITH_RADIUSV11_ONLY +static unsigned int salt_offset = 0; +static uint8_t nullvector[AUTH_VECTOR_LEN] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* for CoA decode */ +#endif + +char const *fr_packet_codes[FR_MAX_PACKET_CODE] = { + "", //!< 0 + "Access-Request", + "Access-Accept", + "Access-Reject", + "Accounting-Request", + "Accounting-Response", + "Accounting-Status", + "Password-Request", + "Password-Accept", + "Password-Reject", + "Accounting-Message", //!< 10 + "Access-Challenge", + "Status-Server", + "Status-Client", + "14", + "15", + "16", + "17", + "18", + "19", + "20", //!< 20 + "Resource-Free-Request", + "Resource-Free-Response", + "Resource-Query-Request", + "Resource-Query-Response", + "Alternate-Resource-Reclaim-Request", + "NAS-Reboot-Request", + "NAS-Reboot-Response", + "28", + "Next-Passcode", + "New-Pin", //!< 30 + "Terminate-Session", + "Password-Expired", + "Event-Request", + "Event-Response", + "35", + "36", + "37", + "38", + "39", + "Disconnect-Request", //!< 40 + "Disconnect-ACK", + "Disconnect-NAK", + "CoA-Request", + "CoA-ACK", + "CoA-NAK", + "46", + "47", + "48", + "49", + "IP-Address-Allocate", + "IP-Address-Release", //!< 50 +}; + + +void fr_printf_log(char const *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if ((fr_debug_lvl == 0) || !fr_log_fp) { + va_end(ap); + return; + } + + vfprintf(fr_log_fp, fmt, ap); + va_end(ap); + + return; +} + +static char const tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + +static void print_hex_data(uint8_t const *ptr, int attrlen, int depth) +{ + int i; + + for (i = 0; i < attrlen; i++) { + if ((i > 0) && ((i & 0x0f) == 0x00)) + fprintf(fr_log_fp, "%.*s", depth, tabs); + fprintf(fr_log_fp, "%02x ", ptr[i]); + if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); + } + if ((i & 0x0f) != 0) fprintf(fr_log_fp, "\n"); +} + + +void rad_print_hex(RADIUS_PACKET const *packet) +{ + int i; + + if (!packet->data || !fr_log_fp) return; + + fprintf(fr_log_fp, " Socket:\t%d\n", packet->sockfd); +#ifdef WITH_TCP + fprintf(fr_log_fp, " Proto:\t%d\n", packet->proto); +#endif + + if (packet->src_ipaddr.af == AF_INET) { + char buffer[32]; + + fprintf(fr_log_fp, " Src IP:\t%s\n", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer))); + fprintf(fr_log_fp, " port:\t%u\n", packet->src_port); + + fprintf(fr_log_fp, " Dst IP:\t%s\n", + inet_ntop(packet->dst_ipaddr.af, + &packet->dst_ipaddr.ipaddr, + buffer, sizeof(buffer))); + fprintf(fr_log_fp, " port:\t%u\n", packet->dst_port); + } + + if (packet->data[0] < FR_MAX_PACKET_CODE) { + fprintf(fr_log_fp, " Code:\t\t(%d) %s\n", packet->data[0], fr_packet_codes[packet->data[0]]); + } else { + fprintf(fr_log_fp, " Code:\t\t%u\n", packet->data[0]); + } + fprintf(fr_log_fp, " Id:\t\t%u\n", packet->data[1]); + fprintf(fr_log_fp, " Length:\t%u\n", ((packet->data[2] << 8) | + (packet->data[3]))); + fprintf(fr_log_fp, " Vector:\t"); + for (i = 4; i < 20; i++) { + fprintf(fr_log_fp, "%02x", packet->data[i]); + } + fprintf(fr_log_fp, "\n"); + + if (packet->data_len > 20) { + int total; + uint8_t const *ptr; + fprintf(fr_log_fp, " Data:"); + + total = packet->data_len - 20; + ptr = packet->data + 20; + + while (total > 0) { + int attrlen; + unsigned int vendor = 0; + + fprintf(fr_log_fp, "\t\t"); + if (total < 2) { /* too short */ + fprintf(fr_log_fp, "%02x\n", *ptr); + break; + } + + if (ptr[1] > total) { /* too long */ + for (i = 0; i < total; i++) { + fprintf(fr_log_fp, "%02x ", ptr[i]); + } + break; + } + + fprintf(fr_log_fp, "%02x %02x ", ptr[0], ptr[1]); + attrlen = ptr[1] - 2; + + if ((ptr[0] == PW_VENDOR_SPECIFIC) && + (attrlen > 4)) { + vendor = (ptr[3] << 16) | (ptr[4] << 8) | ptr[5]; + fprintf(fr_log_fp, "%02x%02x%02x%02x (%u) ", + ptr[2], ptr[3], ptr[4], ptr[5], vendor); + attrlen -= 4; + ptr += 6; + total -= 6; + + } else { + ptr += 2; + total -= 2; + } + + print_hex_data(ptr, attrlen, 3); + + ptr += attrlen; + total -= attrlen; + } + } + fflush(stdout); +} + +/** Wrapper for sendto which handles sendfromto, IPv6, and all possible combinations + * + */ +static int rad_sendto(int sockfd, void *data, size_t data_len, int flags, +#ifdef WITH_UDPFROMTO + fr_ipaddr_t *src_ipaddr, uint16_t src_port, +#else + UNUSED fr_ipaddr_t *src_ipaddr, UNUSED uint16_t src_port, +#endif + fr_ipaddr_t *dst_ipaddr, uint16_t dst_port) +{ + int rcode; + struct sockaddr_storage dst; + socklen_t sizeof_dst; + +#ifdef WITH_UDPFROMTO + struct sockaddr_storage src; + socklen_t sizeof_src; + + fr_ipaddr2sockaddr(src_ipaddr, src_port, &src, &sizeof_src); +#endif + + if (!fr_ipaddr2sockaddr(dst_ipaddr, dst_port, &dst, &sizeof_dst)) { + return -1; + } + +#ifdef WITH_UDPFROMTO + /* + * And if they don't specify a source IP address, don't + * use udpfromto. + */ + if (((dst_ipaddr->af == AF_INET) || (dst_ipaddr->af == AF_INET6)) && + (src_ipaddr->af != AF_UNSPEC) && + !fr_inaddr_any(src_ipaddr)) { + rcode = sendfromto(sockfd, data, data_len, flags, + (struct sockaddr *)&src, sizeof_src, + (struct sockaddr *)&dst, sizeof_dst); + goto done; + } +#endif + + /* + * No udpfromto, fail gracefully. + */ + rcode = sendto(sockfd, data, data_len, flags, + (struct sockaddr *) &dst, sizeof_dst); +#ifdef WITH_UDPFROMTO +done: +#endif + if (rcode < 0) { + fr_strerror_printf("sendto failed: %s", fr_syserror(errno)); + } + + return rcode; +} + + +void rad_recv_discard(int sockfd) +{ + uint8_t header[4]; + struct sockaddr_storage src; + socklen_t sizeof_src = sizeof(src); + + (void) recvfrom(sockfd, header, sizeof(header), 0, + (struct sockaddr *)&src, &sizeof_src); +} + +/** Basic validation of RADIUS packet header + * + * @note fr_strerror errors are only available if fr_debug_lvl > 0. This is to reduce CPU time + * consumed when discarding malformed packet. + * + * @param[in] sockfd we're reading from. + * @param[out] src_ipaddr of the packet. + * @param[out] src_port of the packet. + * @param[out] code Pointer to where to write the packet code. + * @return + * - -1 on failure. + * - 1 on decode error. + * - >= RADIUS_HDR_LEN on success. This is the packet length as specified in the header. + */ +ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, int *code) +{ + ssize_t data_len, packet_len; + uint8_t header[4]; + struct sockaddr_storage src; + socklen_t sizeof_src = sizeof(src); + + data_len = recvfrom(sockfd, header, sizeof(header), MSG_PEEK, (struct sockaddr *)&src, &sizeof_src); + if (data_len < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) return 0; + return -1; + } + + /* + * Convert AF. If unknown, discard packet. + */ + if (!fr_sockaddr2ipaddr(&src, sizeof_src, src_ipaddr, src_port)) { + FR_DEBUG_STRERROR_PRINTF("Unknown address family"); + rad_recv_discard(sockfd); + + return 1; + } + + /* + * Too little data is available, discard the packet. + */ + if (data_len < 4) { + FR_DEBUG_STRERROR_PRINTF("Expected at least 4 bytes of header data, got %zu bytes", data_len); +invalid: + FR_DEBUG_STRERROR_PRINTF("Invalid data from %s: %s", + fr_inet_ntop(src_ipaddr->af, &src_ipaddr->ipaddr), + fr_strerror()); + rad_recv_discard(sockfd); + + return 1; + } + + /* + * See how long the packet says it is. + */ + packet_len = (header[2] * 256) + header[3]; + + /* + * The length in the packet says it's less than + * a RADIUS header length: discard it. + */ + if (packet_len < RADIUS_HDR_LEN) { + FR_DEBUG_STRERROR_PRINTF("Expected at least " STRINGIFY(RADIUS_HDR_LEN) " bytes of packet " + "data, got %zu bytes", packet_len); + goto invalid; + } + + /* + * Enforce RFC requirements, for sanity. + * Anything after 4k will be discarded. + */ + if (packet_len > MAX_PACKET_LEN) { + FR_DEBUG_STRERROR_PRINTF("Length field value too large, expected maximum of " + STRINGIFY(MAX_PACKET_LEN) " bytes, got %zu bytes", packet_len); + goto invalid; + } + + *code = header[0]; + + /* + * The packet says it's this long, but the actual UDP + * size could still be smaller. + */ + return packet_len; +} + + +/** Wrapper for recvfrom, which handles recvfromto, IPv6, and all possible combinations + * + */ +static ssize_t rad_recvfrom(int sockfd, RADIUS_PACKET *packet, int flags, + fr_ipaddr_t *src_ipaddr, uint16_t *src_port, + fr_ipaddr_t *dst_ipaddr, uint16_t *dst_port) +{ + struct sockaddr_storage src; + struct sockaddr_storage dst; + socklen_t sizeof_src = sizeof(src); + socklen_t sizeof_dst = sizeof(dst); + ssize_t data_len; + size_t len; + uint16_t port; + uint8_t buffer[MAX_PACKET_LEN]; + + memset(&src, 0, sizeof_src); + memset(&dst, 0, sizeof_dst); + + /* + * Receive the packet. The OS will discard any data in the + * packet after "len" bytes. + */ +#ifdef WITH_UDPFROMTO + data_len = recvfromto(sockfd, buffer, sizeof(buffer), flags, + (struct sockaddr *)&src, &sizeof_src, + (struct sockaddr *)&dst, &sizeof_dst); +#else + data_len = recvfrom(sockfd, buffer, sizeof(buffer), flags, + (struct sockaddr *)&src, &sizeof_src); + + /* + * Get the destination address, too. + */ + if (getsockname(sockfd, (struct sockaddr *)&dst, + &sizeof_dst) < 0) return -1; +#endif + if (data_len <= 0) { + return data_len; + } + + /* + * See how long the packet says it is. + */ + len = (buffer[2] * 256) + buffer[3]; + + /* + * Header says it's smaller than a RADIUS header, *or* + * the RADIUS header says that the RADIUS packet islarger + * than our buffer. Discard it. + */ + if ((len < RADIUS_HDR_LEN) || (len > (size_t) data_len)) return 0; + + if (!fr_sockaddr2ipaddr(&src, sizeof_src, src_ipaddr, &port)) { + return -1; /* Unknown address family, Die Die Die! */ + } + *src_port = port; + + fr_sockaddr2ipaddr(&dst, sizeof_dst, dst_ipaddr, &port); + *dst_port = port; + + /* + * Different address families should never happen. + */ + if (src.ss_family != dst.ss_family) { + return -1; + } + + packet->data = talloc_memdup(packet, buffer, len); + if (!packet->data) return -1; + + packet->data_len = len; + + /* + * Return the length of the RADIUS packet. There may be + * stuff after the end of the RADIUS packet, so we don't + * want to parse that as RADIUS. + */ + return len; +} + + +#ifndef WITH_RADIUSV11_ONLY +#define AUTH_PASS_LEN (AUTH_VECTOR_LEN) +/** Build an encrypted secret value to return in a reply packet + * + * The secret is hidden by xoring with a MD5 digest created from + * the shared secret and the authentication vector. + * We put them into MD5 in the reverse order from that used when + * encrypting passwords to RADIUS. + */ +static void make_secret(uint8_t *digest, uint8_t const *vector, + char const *secret, uint8_t const *value, size_t length) +{ + FR_MD5_CTX context; + size_t i; + + fr_md5_init(&context); + fr_md5_update(&context, vector, AUTH_VECTOR_LEN); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); + fr_md5_final(digest, &context); + + for ( i = 0; i < length; i++ ) { + digest[i] ^= value[i]; + } + + fr_md5_destroy(&context); +} + +#define MAX_PASS_LEN (128) +static void make_passwd(uint8_t *output, ssize_t *outlen, + uint8_t const *input, size_t inlen, + char const *secret, uint8_t const *vector) +{ + FR_MD5_CTX context, old; + uint8_t digest[AUTH_VECTOR_LEN]; + uint8_t passwd[MAX_PASS_LEN]; + size_t i, n; + size_t len; + + /* + * If the length is zero, round it up. + */ + len = inlen; + + if (len > MAX_PASS_LEN) len = MAX_PASS_LEN; + + memcpy(passwd, input, len); + if (len < sizeof(passwd)) memset(passwd + len, 0, sizeof(passwd) - len); + + if (len == 0) { + len = AUTH_PASS_LEN; + } + + else if ((len & 0x0f) != 0) { + len += 0x0f; + len &= ~0x0f; + } + *outlen = len; + + fr_md5_init(&context); + fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); + fr_md5_copy(old, context); + + /* + * Do first pass. + */ + fr_md5_update(&context, vector, AUTH_PASS_LEN); + + for (n = 0; n < len; n += AUTH_PASS_LEN) { + if (n > 0) { + fr_md5_copy(context, old); + fr_md5_update(&context, + passwd + n - AUTH_PASS_LEN, + AUTH_PASS_LEN); + } + + fr_md5_final(digest, &context); + for (i = 0; i < AUTH_PASS_LEN; i++) { + passwd[i + n] ^= digest[i]; + } + } + + memcpy(output, passwd, len); + + fr_md5_destroy(&old); + fr_md5_destroy(&context); +} + + +static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, + uint8_t const *input, size_t inlen, size_t room, + char const *secret, uint8_t const *vector) +{ + FR_MD5_CTX context, old; + uint8_t digest[AUTH_VECTOR_LEN]; + size_t i, n; + size_t encrypted_len; + + /* + * The password gets encoded with a 1-byte "length" + * field. Ensure that it doesn't overflow. + */ + if (room > 253) room = 253; + + /* + * Limit the maximum size of the input password. 2 bytes + * are taken up by the salt, and one by the encoded + * "length" field. Note that if we have a tag, the + * "room" will be 252 octets, not 253 octets. + */ + if (inlen > (room - 3)) inlen = room - 3; + + /* + * Length of the encrypted data is the clear-text + * password length plus one byte which encodes the length + * of the password. We round up to the nearest encoding + * block. Note that this can result in the encoding + * length being more than 253 octets. + */ + encrypted_len = inlen + 1; + if ((encrypted_len & 0x0f) != 0) { + encrypted_len += 0x0f; + encrypted_len &= ~0x0f; + } + + /* + * We need 2 octets for the salt, followed by the actual + * encrypted data. + */ + if (encrypted_len > (room - 2)) encrypted_len = room - 2; + + *outlen = encrypted_len + 2; /* account for the salt */ + + /* + * Copy the password over, and zero-fill the remainder. + */ + memcpy(output + 3, input, inlen); + memset(output + 3 + inlen, 0, *outlen - 3 - inlen); + + /* + * Generate salt. The RFCs say: + * + * The high bit of salt[0] must be set, each salt in a + * packet should be unique, and they should be random + * + * So, we set the high bit, add in a counter, and then + * add in some CSPRNG data. should be OK.. + */ + output[0] = (0x80 | ( ((salt_offset++) & 0x0f) << 3) | + (fr_rand() & 0x07)); + output[1] = fr_rand(); + output[2] = inlen; /* length of the password string */ + + fr_md5_init(&context); + fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); + fr_md5_copy(old, context); + + fr_md5_update(&context, vector, AUTH_VECTOR_LEN); + fr_md5_update(&context, &output[0], 2); + + for (n = 0; n < encrypted_len; n += AUTH_PASS_LEN) { + size_t block_len; + + if (n > 0) { + fr_md5_copy(context, old); + fr_md5_update(&context, + output + 2 + n - AUTH_PASS_LEN, + AUTH_PASS_LEN); + } + + fr_md5_final(digest, &context); + + if ((2 + n + AUTH_PASS_LEN) < room) { + block_len = AUTH_PASS_LEN; + } else { + block_len = room - 2 - n; + } + + for (i = 0; i < block_len; i++) { + output[i + 2 + n] ^= digest[i]; + } + } + fr_md5_destroy(&old); + fr_md5_destroy(&context); +} +#endif /* WITH_RADIUSV11_ONLY */ + +static int do_next_tlv(VALUE_PAIR const *vp, VALUE_PAIR const *next, int nest) +{ + unsigned int tlv1, tlv2; + + if (nest > fr_attr_max_tlv) return 0; + + if (!vp) return 0; + + /* + * Keep encoding TLVs which have the same scope. + * e.g. two attributes of: + * ATTR.TLV1.TLV2.TLV3 = data1 + * ATTR.TLV1.TLV2.TLV4 = data2 + * both get put into a container of "ATTR.TLV1.TLV2" + */ + + /* + * Nothing to follow, we're done. + */ + if (!next) return 0; + + /* + * Not from the same vendor, skip it. + */ + if (vp->da->vendor != next->da->vendor) return 0; + + /* + * In a different TLV space, skip it. + */ + tlv1 = vp->da->attr; + tlv2 = next->da->attr; + + tlv1 &= ((1 << fr_attr_shift[nest]) - 1); + tlv2 &= ((1 << fr_attr_shift[nest]) - 1); + + if (tlv1 != tlv2) return 0; + + return 1; +} + + +static ssize_t vp2data_any(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, int nest, + VALUE_PAIR const **pvp, + uint8_t *start, size_t room); + +static ssize_t vp2attr_rfc(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, VALUE_PAIR const **pvp, + unsigned int attribute, uint8_t *ptr, size_t room); + +/** Encode the *data* portion of the TLV + * + * This is really a sub-function of vp2data_any(). It encodes the *data* portion + * of the TLV, and assumes that the encapsulating attribute has already been encoded. + */ +static ssize_t vp2data_tlvs(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, int nest, + VALUE_PAIR const **pvp, + uint8_t *start, size_t room) +{ + ssize_t len; + size_t my_room; + uint8_t *ptr = start; + VALUE_PAIR const *vp = *pvp; + VALUE_PAIR const *svp = vp; + + if (!svp) return 0; + +#ifndef NDEBUG + if (nest > fr_attr_max_tlv) { + fr_strerror_printf("vp2data_tlvs: attribute nesting overflow"); + return -1; + } +#endif + + while (vp) { + VERIFY_VP(vp); + + if (room <= 2) return ptr - start; + + ptr[0] = (vp->da->attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]; + ptr[1] = 2; + + my_room = room; + if (room > 255) my_room = 255; + + len = vp2data_any(packet, original, secret, nest, + &vp, ptr + 2, my_room - 2); + if (len < 0) return len; + if (len == 0) return ptr - start; + /* len can NEVER be more than 253 */ + + ptr[1] += len; + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) { + fprintf(fr_log_fp, "\t\t%02x %02x ", ptr[0], ptr[1]); + print_hex_data(ptr + 2, len, 3); + } +#endif + + room -= ptr[1]; + ptr += ptr[1]; + *pvp = vp; + + if (!do_next_tlv(svp, vp, nest)) break; + } + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) { + DICT_ATTR const *da; + + da = dict_attrbyvalue(svp->da->attr & ((1 << fr_attr_shift[nest ]) - 1), svp->da->vendor); + if (da) fprintf(fr_log_fp, "\t%s = ...\n", da->name); + } +#endif + + return ptr - start; +} + +/** Encodes the data portion of an attribute + * + * @return -1 on error, or the length of the data portion. + */ +static ssize_t vp2data_any(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, int nest, + VALUE_PAIR const **pvp, + uint8_t *start, size_t room) +{ + uint32_t lvalue; + ssize_t len; + uint8_t const *data; + uint8_t *ptr = start; + uint8_t array[4]; + uint64_t lvalue64; + VALUE_PAIR const *vp = *pvp; + + VERIFY_VP(vp); + + /* + * See if we need to encode a TLV. The low portion of + * the attribute has already been placed into the packer. + * If there are still attribute bytes left, then go + * encode them as TLVs. + * + * If we cared about the stack, we could unroll the loop. + */ + if (vp->da->flags.is_tlv && (nest < fr_attr_max_tlv) && + ((vp->da->attr >> fr_attr_shift[nest + 1]) != 0)) { + return vp2data_tlvs(packet, original, secret, nest + 1, pvp, + start, room); + } + + /* + * Set up the default sources for the data. + */ + len = vp->vp_length; + + switch (vp->da->type) { + case PW_TYPE_STRING: + case PW_TYPE_OCTETS: + data = vp->data.ptr; + if (!data) return 0; + break; + + case PW_TYPE_IFID: + case PW_TYPE_IPV4_ADDR: + case PW_TYPE_IPV6_ADDR: + case PW_TYPE_IPV6_PREFIX: + case PW_TYPE_IPV4_PREFIX: + case PW_TYPE_ABINARY: + case PW_TYPE_ETHERNET: /* just in case */ + data = (uint8_t const *) &vp->data; + break; + + case PW_TYPE_BYTE: + len = 1; /* just in case */ + array[0] = vp->vp_byte; + data = array; + break; + + case PW_TYPE_SHORT: + len = 2; /* just in case */ + array[0] = (vp->vp_short >> 8) & 0xff; + array[1] = vp->vp_short & 0xff; + data = array; + break; + + case PW_TYPE_INTEGER: + len = 4; /* just in case */ + lvalue = htonl(vp->vp_integer); + memcpy(array, &lvalue, sizeof(lvalue)); + data = array; + break; + + case PW_TYPE_INTEGER64: + len = 8; /* just in case */ + lvalue64 = htonll(vp->vp_integer64); + data = (uint8_t *) &lvalue64; + break; + + /* + * There are no tagged date attributes. + */ + case PW_TYPE_DATE: + lvalue = htonl(vp->vp_date); + data = (uint8_t const *) &lvalue; + len = 4; /* just in case */ + break; + + case PW_TYPE_SIGNED: + { + int32_t slvalue; + + len = 4; /* just in case */ + slvalue = htonl(vp->vp_signed); + memcpy(array, &slvalue, sizeof(slvalue)); + data = array; + break; + } + + default: /* unknown type: ignore it */ + fr_strerror_printf("ERROR: Unknown attribute type %d", vp->da->type); + return -1; + } + + /* + * No data: skip it. + */ + if (len == 0) { + *pvp = vp->next; + return 0; + } + + /* + * Bound the data to the calling size + */ + if (len > (ssize_t) room) len = room; + +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 does not encrypt any attributes. + */ + if (packet->radiusv11) goto tag; +#endif + + /* + * Encrypt the various password styles + * + * Attributes with encrypted values MUST be less than + * 128 bytes long. + */ + switch (vp->da->flags.encrypt) { +#ifndef WITH_RADIUSV11_ONLY + case FLAG_ENCRYPT_USER_PASSWORD: + make_passwd(ptr, &len, data, len, + secret, packet->vector); + break; + + case FLAG_ENCRYPT_TUNNEL_PASSWORD: + lvalue = 0; + if (vp->da->flags.has_tag) lvalue = 1; + + /* + * Check if there's enough room. If there isn't, + * we discard the attribute. + * + * This is ONLY a problem if we have multiple VSA's + * in one Vendor-Specific, though. + */ + if (room < (18 + lvalue)) return 0; + + switch (packet->code) { + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + default: + if (!original) { + fr_strerror_printf("ERROR: No request packet, cannot encrypt %s attribute in the vp.", vp->da->name); + return -1; + } + + make_tunnel_passwd(ptr + lvalue, &len, data, len, + room - lvalue, + secret, original->vector); + break; + case PW_CODE_ACCOUNTING_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: + case PW_CODE_COA_REQUEST: + make_tunnel_passwd(ptr + lvalue, &len, data, len, + room - lvalue, + secret, packet->vector); + break; + } + if (lvalue) ptr[0] = TAG_VALID(vp->tag) ? vp->tag : TAG_NONE; + len += lvalue; + break; + + /* + * The code above ensures that this attribute + * always fits. + */ + case FLAG_ENCRYPT_ASCEND_SECRET: + if (len > AUTH_VECTOR_LEN) len = AUTH_VECTOR_LEN; + make_secret(ptr, packet->vector, secret, data, len); + len = AUTH_VECTOR_LEN; + break; +#endif /* WITH_RADIUSV11_ONLY */ + + default: +#ifdef WITH_RADIUSV11 + tag: +#endif + if (vp->da->flags.has_tag && TAG_VALID(vp->tag)) { + if (vp->da->type == PW_TYPE_STRING) { + if (len > ((ssize_t) (room - 1))) len = room - 1; + ptr[0] = vp->tag; + ptr++; + } else if (vp->da->type == PW_TYPE_INTEGER) { + array[0] = vp->tag; + } /* else it can't be any other type */ + } + memcpy(ptr, data, len); + break; + } /* switch over encryption flags */ + + *pvp = vp->next; + return len + (ptr - start); +} + +static ssize_t attr_shift(uint8_t const *start, uint8_t const *end, + uint8_t *ptr, int hdr_len, ssize_t len, + int flag_offset, int vsa_offset) +{ + int check_len = len - ptr[1]; + int total = len + hdr_len; + + /* + * Pass 1: Check if the addition of the headers + * overflows the available room. If so, return + * what we were capable of encoding. + */ + + while (check_len > (255 - hdr_len)) { + total += hdr_len; + check_len -= (255 - hdr_len); + } + + /* + * Note that this results in a number of attributes maybe + * being marked as "encoded", but which aren't in the + * packet. Oh well. The solution is to fix the + * "vp2data_any" function to take into account the header + * lengths. + */ + if ((ptr + ptr[1] + total) > end) { + return (ptr + ptr[1]) - start; + } + + /* + * Pass 2: Now that we know there's enough room, + * re-arrange the data to form a set of valid + * RADIUS attributes. + */ + while (1) { + int sublen = 255 - ptr[1]; + + if (len <= sublen) { + break; + } + + len -= sublen; + memmove(ptr + 255 + hdr_len, ptr + 255, sublen); + memmove(ptr + 255, ptr, hdr_len); + ptr[1] += sublen; + if (vsa_offset) ptr[vsa_offset] += sublen; + ptr[flag_offset] |= 0x80; + + ptr += 255; + ptr[1] = hdr_len; + if (vsa_offset) ptr[vsa_offset] = 3; + } + + ptr[1] += len; + if (vsa_offset) ptr[vsa_offset] += len; + + return (ptr + ptr[1]) - start; +} + + +/** Encode an "extended" attribute + */ +int rad_vp2extended(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, VALUE_PAIR const **pvp, + uint8_t *ptr, size_t room) +{ + int len; + int hdr_len; + uint8_t *start = ptr; + VALUE_PAIR const *vp = *pvp; + + VERIFY_VP(vp); + + if (!vp->da->flags.extended) { + fr_strerror_printf("rad_vp2extended called for non-extended attribute"); + return -1; + } + + /* + * The attribute number is encoded into the upper 8 bits + * of the vendor ID. + */ + ptr[0] = (vp->da->vendor / FR_MAX_VENDOR) & 0xff; + + if (!vp->da->flags.long_extended) { + if (room < 3) return 0; + + ptr[1] = 3; + ptr[2] = vp->da->attr & fr_attr_mask[0]; + + } else { + if (room < 4) return 0; + + ptr[1] = 4; + ptr[2] = vp->da->attr & fr_attr_mask[0]; + ptr[3] = 0; + } + + /* + * Only "flagged" attributes can be longer than one + * attribute. + */ + if (!vp->da->flags.long_extended && (room > 255)) { + room = 255; + } + + /* + * Handle EVS VSAs. + */ + if (vp->da->flags.evs) { + uint8_t *evs = ptr + ptr[1]; + + if (room < (size_t) (ptr[1] + 5)) return 0; + + ptr[2] = 26; + + evs[0] = 0; /* always zero */ + evs[1] = (vp->da->vendor >> 16) & 0xff; + evs[2] = (vp->da->vendor >> 8) & 0xff; + evs[3] = vp->da->vendor & 0xff; + evs[4] = vp->da->attr & fr_attr_mask[0]; + + ptr[1] += 5; + } + hdr_len = ptr[1]; + + len = vp2data_any(packet, original, secret, 0, + pvp, ptr + ptr[1], room - hdr_len); + if (len <= 0) return len; + + /* + * There may be more than 252 octets of data encoded in + * the attribute. If so, move the data up in the packet, + * and copy the existing header over. Set the "M" flag ONLY + * after copying the rest of the data. + */ + if (vp->da->flags.long_extended && (len > (255 - ptr[1]))) { + return attr_shift(start, start + room, ptr, 4, len, 3, 0); + } + + ptr[1] += len; + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) { + int jump = 3; + + fprintf(fr_log_fp, "\t\t%02x %02x ", ptr[0], ptr[1]); + if (!vp->da->flags.long_extended) { + fprintf(fr_log_fp, "%02x ", ptr[2]); + + } else { + fprintf(fr_log_fp, "%02x %02x ", ptr[2], ptr[3]); + jump = 4; + } + + if (vp->da->flags.evs) { + fprintf(fr_log_fp, "%02x%02x%02x%02x (%u) %02x ", + ptr[jump], ptr[jump + 1], + ptr[jump + 2], ptr[jump + 3], + ((ptr[jump + 1] << 16) | + (ptr[jump + 2] << 8) | + ptr[jump + 3]), + ptr[jump + 4]); + jump += 5; + } + + print_hex_data(ptr + jump, len, 3); + } +#endif + + return (ptr + ptr[1]) - start; +} + + +/** Encode a WiMAX attribute + * + */ +int rad_vp2wimax(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, VALUE_PAIR const **pvp, + uint8_t *ptr, size_t room) +{ + int len; + uint32_t lvalue; + int hdr_len; + uint8_t *start = ptr; + VALUE_PAIR const *vp = *pvp; + + VERIFY_VP(vp); + + /* + * Double-check for WiMAX format. + */ + if (!vp->da->flags.wimax) { + fr_strerror_printf("rad_vp2wimax called for non-WIMAX VSA"); + return -1; + } + + /* + * Not enough room for: + * attr, len, vendor-id, vsa, vsalen, continuation + */ + if (room < 9) return 0; + + /* + * Build the Vendor-Specific header + */ + ptr = start; + ptr[0] = PW_VENDOR_SPECIFIC; + ptr[1] = 9; + lvalue = htonl(vp->da->vendor); + memcpy(ptr + 2, &lvalue, 4); + ptr[6] = (vp->da->attr & fr_attr_mask[1]); + ptr[7] = 3; + ptr[8] = 0; /* continuation byte */ + + hdr_len = 9; + + len = vp2data_any(packet, original, secret, 0, pvp, ptr + ptr[1], + room - hdr_len); + if (len <= 0) return len; + + /* + * There may be more than 252 octets of data encoded in + * the attribute. If so, move the data up in the packet, + * and copy the existing header over. Set the "C" flag + * ONLY after copying the rest of the data. + */ + if (len > (255 - ptr[1])) { + return attr_shift(start, start + room, ptr, hdr_len, len, 8, 7); + } + + ptr[1] += len; + ptr[7] += len; + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) { + fprintf(fr_log_fp, "\t\t%02x %02x %02x%02x%02x%02x (%u) %02x %02x %02x ", + ptr[0], ptr[1], + ptr[2], ptr[3], ptr[4], ptr[5], + (ptr[3] << 16) | (ptr[4] << 8) | ptr[5], + ptr[6], ptr[7], ptr[8]); + print_hex_data(ptr + 9, len, 3); + } +#endif + + return (ptr + ptr[1]) - start; +} + +/** Encode an RFC format attribute, with the "concat" flag set + * + * If there isn't enough room in the packet, the data is + * truncated to fit. + */ +static ssize_t vp2attr_concat(UNUSED RADIUS_PACKET const *packet, + UNUSED RADIUS_PACKET const *original, + UNUSED char const *secret, VALUE_PAIR const **pvp, + unsigned int attribute, uint8_t *start, size_t room) +{ + uint8_t *ptr = start; + uint8_t const *p; + size_t len, left; + VALUE_PAIR const *vp = *pvp; + + VERIFY_VP(vp); + + p = vp->vp_octets; + len = vp->vp_length; + + while (len > 0) { + if (room <= 2) break; + + ptr[0] = attribute; + ptr[1] = 2; + + left = len; + + /* no more than 253 octets */ + if (left > 253) left = 253; + + /* no more than "room" octets */ + if (room < (left + 2)) left = room - 2; + + memcpy(ptr + 2, p, left); + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) { + fprintf(fr_log_fp, "\t\t%02x %02x ", ptr[0], ptr[1]); + print_hex_data(ptr + 2, len, 3); + } +#endif + ptr[1] += left; + ptr += ptr[1]; + p += left; + room -= left; + len -= left; + } + + *pvp = vp->next; + return ptr - start; +} + +/** Encode an RFC format TLV. + * + * This could be a standard attribute, or a TLV data type. + * If it's a standard attribute, then vp->da->attr == attribute. + * Otherwise, attribute may be something else. + */ +static ssize_t vp2attr_rfc(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, VALUE_PAIR const **pvp, + unsigned int attribute, uint8_t *ptr, size_t room) +{ + ssize_t len; + + if (room <= 2) return 0; + + ptr[0] = attribute & 0xff; + ptr[1] = 2; + + if (room > 255) room = 255; + + len = vp2data_any(packet, original, secret, 0, pvp, ptr + ptr[1], room - ptr[1]); + if (len <= 0) return len; + + ptr[1] += len; + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) { + fprintf(fr_log_fp, "\t\t%02x %02x ", ptr[0], ptr[1]); + print_hex_data(ptr + 2, len, 3); + } +#endif + + return ptr[1]; +} + + +/** Encode a VSA which is a TLV + * + * If it's in the RFC format, call vp2attr_rfc. Otherwise, encode it here. + */ +static ssize_t vp2attr_vsa(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, VALUE_PAIR const **pvp, + unsigned int attribute, unsigned int vendor, + uint8_t *ptr, size_t room) +{ + ssize_t len; + DICT_VENDOR *dv; + VALUE_PAIR const *vp = *pvp; + + VERIFY_VP(vp); + /* + * Unknown vendor: RFC format. + * Known vendor and RFC format: go do that. + */ + dv = dict_vendorbyvalue(vendor); + if (!dv || + (!vp->da->flags.is_tlv && (dv->type == 1) && (dv->length == 1))) { + return vp2attr_rfc(packet, original, secret, pvp, + attribute, ptr, room); + } + + switch (dv->type) { + default: + fr_strerror_printf("vp2attr_vsa: Internal sanity check failed," + " type %u", (unsigned) dv->type); + return -1; + + case 4: + ptr[0] = 0; /* attr must be 24-bit */ + ptr[1] = (attribute >> 16) & 0xff; + ptr[2] = (attribute >> 8) & 0xff; + ptr[3] = attribute & 0xff; + break; + + case 2: + ptr[0] = (attribute >> 8) & 0xff; + ptr[1] = attribute & 0xff; + break; + + case 1: + ptr[0] = attribute & 0xff; + break; + } + + switch (dv->length) { + default: + fr_strerror_printf("vp2attr_vsa: Internal sanity check failed," + " length %u", (unsigned) dv->length); + return -1; + + case 0: + break; + + case 2: + ptr[dv->type] = 0; + ptr[dv->type + 1] = dv->type + 2; + break; + + case 1: + ptr[dv->type] = dv->type + 1; + break; + + } + + if (room > 255) room = 255; + + len = vp2data_any(packet, original, secret, 0, pvp, + ptr + dv->type + dv->length, room - (dv->type + dv->length)); + if (len <= 0) return len; + + if (dv->length) ptr[dv->type + dv->length - 1] += len; + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) { + switch (dv->type) { + default: + break; + + case 4: + if ((fr_debug_lvl > 3) && fr_log_fp) + fprintf(fr_log_fp, "\t\t%02x%02x%02x%02x ", + ptr[0], ptr[1], ptr[2], ptr[3]); + break; + + case 2: + if ((fr_debug_lvl > 3) && fr_log_fp) + fprintf(fr_log_fp, "\t\t%02x%02x ", + ptr[0], ptr[1]); + break; + + case 1: + if ((fr_debug_lvl > 3) && fr_log_fp) + fprintf(fr_log_fp, "\t\t%02x ", ptr[0]); + break; + } + + switch (dv->length) { + default: + break; + + case 0: + fprintf(fr_log_fp, " "); + break; + + case 1: + fprintf(fr_log_fp, "%02x ", + ptr[dv->type]); + break; + + case 2: + fprintf(fr_log_fp, "%02x%02x ", + ptr[dv->type], ptr[dv->type] + 1); + break; + } + + print_hex_data(ptr + dv->type + dv->length, len, 3); + } +#endif + + return dv->type + dv->length + len; +} + + +/** Encode a Vendor-Specific attribute + * + */ +int rad_vp2vsa(RADIUS_PACKET const *packet, RADIUS_PACKET const *original, + char const *secret, VALUE_PAIR const **pvp, uint8_t *ptr, + size_t room) +{ + ssize_t len; + uint32_t lvalue; + VALUE_PAIR const *vp = *pvp; + + VERIFY_VP(vp); + + if (vp->da->vendor == 0) { + fr_strerror_printf("rad_vp2vsa called with rfc attribute"); + return -1; + } + + /* + * Double-check for WiMAX format. + */ + if (vp->da->flags.wimax) { + return rad_vp2wimax(packet, original, secret, pvp, ptr, room); + } + + if (vp->da->vendor > FR_MAX_VENDOR) { + fr_strerror_printf("rad_vp2vsa: Invalid arguments"); + return -1; + } + + /* + * Not enough room for: + * attr, len, vendor-id + */ + if (room < 6) return 0; + + /* + * Build the Vendor-Specific header + */ + ptr[0] = PW_VENDOR_SPECIFIC; + ptr[1] = 6; + lvalue = htonl(vp->da->vendor); + memcpy(ptr + 2, &lvalue, 4); + + if (room > 255) room = 255; + + len = vp2attr_vsa(packet, original, secret, pvp, + vp->da->attr, vp->da->vendor, + ptr + ptr[1], room - ptr[1]); + if (len < 0) return len; + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) { + fprintf(fr_log_fp, "\t\t%02x %02x %02x%02x%02x%02x (%u) ", + ptr[0], ptr[1], + ptr[2], ptr[3], ptr[4], ptr[5], + (ptr[3] << 16) | (ptr[4] << 8) | ptr[5]); + print_hex_data(ptr + 6, len, 3); + } +#endif + + ptr[1] += len; + + return ptr[1]; +} + + +/** Encode an RFC standard attribute 1..255 + * + */ +int rad_vp2rfc(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, VALUE_PAIR const **pvp, + uint8_t *ptr, size_t room) +{ + VALUE_PAIR const *vp = *pvp; + + VERIFY_VP(vp); + + if (room < 2) return -1; + + if (vp->da->vendor != 0) { + fr_strerror_printf("rad_vp2rfc called with VSA"); + return -1; + } + + if ((vp->da->attr == 0) || (vp->da->attr > 255)) { + fr_strerror_printf("rad_vp2rfc called with non-standard attribute %u", vp->da->attr); + return -1; + } + + /* + * Only CUI is allowed to have zero length. + * Thank you, WiMAX! + */ + if ((vp->vp_length == 0) && + (vp->da->attr == PW_CHARGEABLE_USER_IDENTITY)) { + ptr[0] = PW_CHARGEABLE_USER_IDENTITY; + ptr[1] = 2; + + *pvp = vp->next; + return 2; + } + + /* + * Message-Authenticator is hard-coded. + */ + if (vp->da->attr == PW_MESSAGE_AUTHENTICATOR) { +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 does not encode or verify Message-Authenticator. + */ + if (packet->radiusv11) { + *pvp = (*pvp)->next; + return 0; + } +#endif + + if (room < 18) return -1; + + ptr[0] = PW_MESSAGE_AUTHENTICATOR; + ptr[1] = 18; + memset(ptr + 2, 0, 16); +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) { + fprintf(fr_log_fp, "\t\t50 12 ...\n"); + } +#endif + + *pvp = (*pvp)->next; + return 18; + } + + /* + * Hacks for NAS-Filter-Rule. They all get concatenated + * with 0x00 bytes in between the values. We rely on the + * decoder to do the opposite transformation on incoming + * packets. + */ + if (vp->da->attr == PW_NAS_FILTER_RULE) { + uint8_t const *end = ptr + room; + uint8_t *p, *attr = ptr; + bool zero = false; + + attr[0] = PW_NAS_FILTER_RULE; + attr[1] = 2; + p = ptr + 2; + + while (vp && !vp->da->vendor && (vp->da->attr == PW_NAS_FILTER_RULE)) { + if ((p + zero + vp->vp_length) > end) { + break; + } + + if (zero) { + if (attr[1] == 255) { + attr = p; + if ((attr + 3) >= end) break; + + attr[0] = PW_NAS_FILTER_RULE; + attr[1] = 2; + p = attr + 2; + } + + *(p++) = 0; + attr[1]++; + } + + /* + * Check for overflow + */ + if ((attr[1] + vp->vp_length) < 255) { + memcpy(p, vp->vp_strvalue, vp->vp_length); + attr[1] += vp->vp_length; + p += vp->vp_length; + + } else if (attr + (attr[1] + 2 + vp->vp_length) > end) { + break; + + } else if (vp->vp_length > 253) { + /* + * Drop VPs which are too long. + * We don't (yet) split one VP + * across multiple attributes. + */ + vp = vp->next; + continue; + + } else { + size_t first, second; + + first = 255 - attr[1]; + second = vp->vp_length - first; + + memcpy(p, vp->vp_strvalue, first); + p += first; + attr[1] = 255; + attr = p; + + attr[0] = PW_NAS_FILTER_RULE; + attr[1] = 2; + p = attr + 2; + + memcpy(p, vp->vp_strvalue + first, second); + attr[1] += second; + p += second; + } + + vp = vp->next; + zero = true; + } + + *pvp = vp; + return p - ptr; + } + + /* + * EAP-Message is special. + */ + if (vp->da->flags.concat && (vp->vp_length > 253)) { + return vp2attr_concat(packet, original, secret, pvp, vp->da->attr, + ptr, room); + } + + return vp2attr_rfc(packet, original, secret, pvp, vp->da->attr, + ptr, room); +} + +static ssize_t rad_vp2rfctlv(RADIUS_PACKET const *packet, + RADIUS_PACKET const *original, + char const *secret, VALUE_PAIR const **pvp, + uint8_t *start, size_t room) +{ + ssize_t len; + VALUE_PAIR const *vp = *pvp; + + VERIFY_VP(vp); + + if (!vp->da->flags.is_tlv) { + fr_strerror_printf("rad_vp2rfctlv: attr is not a TLV"); + return -1; + } + + if ((vp->da->vendor & (FR_MAX_VENDOR - 1)) != 0) { + fr_strerror_printf("rad_vp2rfctlv: attr is not an RFC TLV"); + return -1; + } + + if (room < 5) return 0; + + /* + * Encode the first level of TLVs + */ + start[0] = (vp->da->vendor / FR_MAX_VENDOR) & 0xff; + start[1] = 4; + start[2] = vp->da->attr & fr_attr_mask[0]; + start[3] = 2; + + len = vp2data_any(packet, original, secret, 0, pvp, + start + 4, room - 4); + if (len <= 0) return len; + + if (len > 253) { + return -1; + } + + start[1] += len; + start[3] += len; + + return start[1]; +} + +/** Parse a data structure into a RADIUS attribute + * + */ +int rad_vp2attr(RADIUS_PACKET const *packet, RADIUS_PACKET const *original, + char const *secret, VALUE_PAIR const **pvp, uint8_t *start, + size_t room) +{ + VALUE_PAIR const *vp; + + if (!pvp || !*pvp || !start || (room <= 2)) return -1; + + vp = *pvp; + + VERIFY_VP(vp); + + /* + * RFC format attributes take the fast path. + */ + if (!vp->da->vendor) { + if (vp->da->attr > 255) { + *pvp = vp->next; + return 0; + } + + return rad_vp2rfc(packet, original, secret, pvp, + start, room); + } + + if (vp->da->flags.extended) { + return rad_vp2extended(packet, original, secret, pvp, + start, room); + } + + /* + * The upper 8 bits of the vendor number are the standard + * space attribute which is a TLV. + */ + if ((vp->da->vendor & (FR_MAX_VENDOR - 1)) == 0) { + return rad_vp2rfctlv(packet, original, secret, pvp, + start, room); + } + + if (vp->da->flags.wimax) { + return rad_vp2wimax(packet, original, secret, pvp, + start, room); + } + + return rad_vp2vsa(packet, original, secret, pvp, start, room); +} + + +/** Encode a packet + * + */ +int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + char const *secret) +{ + radius_packet_t *hdr; + uint8_t *ptr; + uint16_t total_length; + int len; + VALUE_PAIR const *reply; + + /* + * A 4K packet, aligned on 64-bits. + */ + uint64_t data[MAX_PACKET_LEN / sizeof(uint64_t)]; + + /* + * Double-check some things based on packet code. + */ + switch (packet->code) { + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + if (!original) { + fr_strerror_printf("ERROR: Cannot sign response packet without a request packet"); + return -1; + } + break; + + /* + * These packet vectors start off as all zero. + */ + case PW_CODE_ACCOUNTING_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: + case PW_CODE_COA_REQUEST: + memset(packet->vector, 0, sizeof(packet->vector)); + break; + + default: + break; + } + + /* + * Use memory on the stack, until we know how + * large the packet will be. + */ + hdr = (radius_packet_t *) data; + + /* + * Build standard header + */ + hdr->code = packet->code; + +#ifdef WITH_RADIUSV11 + if (packet->radiusv11) { + uint32_t id = packet->id; + + hdr->id = 0; + + id = htonl(id); + memcpy(hdr->vector, &id, sizeof(id)); + memset(hdr->vector + sizeof(id), 0, sizeof(hdr->vector) - sizeof(id)); + } else +#endif + { + hdr->id = packet->id; + + memcpy(hdr->vector, packet->vector, sizeof(hdr->vector)); + } + + total_length = RADIUS_HDR_LEN; + + /* + * Load up the configuration values for the user + */ + ptr = hdr->data; + packet->offset = 0; + + /* + * FIXME: Loop twice over the reply list. The first time, + * calculate the total length of data. The second time, + * allocate the memory, and fill in the VP's. + * + * Hmm... this may be slower than just doing a small + * memcpy. + */ + + /* + * Loop over the reply attributes for the packet. + */ + reply = packet->vps; + while (reply) { + size_t last_len, room; + char const *last_name = NULL; + + VERIFY_VP(reply); + + /* + * Ignore non-wire attributes, but allow extended + * attributes. + */ + if ((reply->da->vendor == 0) && + ((reply->da->attr & 0xFFFF) >= 256) && + !reply->da->flags.extended && !reply->da->flags.long_extended) { +#ifndef NDEBUG + /* + * Permit the admin to send BADLY formatted + * attributes with a debug build. + */ + if (reply->da->attr == PW_RAW_ATTRIBUTE) { + memcpy(ptr, reply->vp_octets, reply->vp_length); + len = reply->vp_length; + reply = reply->next; + goto next; + } +#endif + reply = reply->next; + continue; + } + +#ifdef WITH_RADIUSV11 + /* + * Do not encode Message-Authenticator for RADIUS/1.1 + */ + if ((reply->da->vendor == 0) && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) { + reply = reply->next; + continue; + } + + + /* + * Do not encode Original-Packet-Code for RADIUS/1.1 + */ + if (reply->da->vendor == ((unsigned int) PW_EXTENDED_ATTRIBUTE_1 << 24) && (reply->da->attr == 4)) { + reply = reply->next; + continue; + } +#endif + + /* + * We allow zero-length strings in "unlang", but + * skip them (except for CUI, thanks WiMAX!) on + * all other attributes. + */ + if (reply->vp_length == 0) { + if ((reply->da->vendor != 0) || + ((reply->da->attr != PW_CHARGEABLE_USER_IDENTITY) && + (reply->da->attr != PW_MESSAGE_AUTHENTICATOR))) { + reply = reply->next; + continue; + } + } + + /* + * How much room do we have left? + */ + room = ((uint8_t *) data) + sizeof(data) - ptr; + + /* + * Set the Message-Authenticator to the correct + * length and initial value. + */ + if (!reply->da->vendor && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) { +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 does not encode or verify Message-Authenticator. + */ + if (packet->radiusv11) { + reply = reply->next; + continue; + } +#endif + + if (room < 18) break; + + /* + * Cache the offset to the + * Message-Authenticator + */ + packet->offset = total_length; + last_len = 16; + } else { + if (room < (2 + reply->vp_length)) break; + + last_len = reply->vp_length; + } + last_name = reply->da->name; + + /* + * Note that this also checks "room", as the + * attribute may be a VSA, etc. + */ + len = rad_vp2attr(packet, original, secret, &reply, ptr, room); + if (len < 0) return -1; + + /* + * Failed to encode the attribute, likely because + * the packet is full. + */ + if (len == 0) { + if (last_len != 0) { + fr_strerror_printf("WARNING: Failed encoding attribute %s\n", last_name); + break; + } else { + fr_strerror_printf("WARNING: Skipping zero-length attribute %s\n", last_name); + } + } + +#ifndef NDEBUG + next: /* Used only for Raw-Attribute */ +#endif + ptr += len; + total_length += len; + } /* done looping over all attributes */ + + /* + * Fill in the rest of the fields, and copy the data over + * from the local stack to the newly allocated memory. + * + * Yes, all this 'memcpy' is slow, but it means + * that we only allocate the minimum amount of + * memory for a request. + */ + packet->data_len = total_length; + packet->data = talloc_array(packet, uint8_t, packet->data_len); + if (!packet->data) { + fr_strerror_printf("Out of memory"); + return -1; + } + + memcpy(packet->data, hdr, packet->data_len); + hdr = (radius_packet_t *) packet->data; + + total_length = htons(total_length); + memcpy(hdr->length, &total_length, sizeof(total_length)); + + return 0; +} + +#ifdef WITH_RADIUSV11_ONLY +#define RADIUSV11_UNUSED UNUSED +#else +#define RADIUSV11_UNUSED +#endif + +/** Sign a previously encoded packet + * + */ +int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + RADIUSV11_UNUSED char const *secret) +{ + radius_packet_t *hdr = (radius_packet_t *)packet->data; + + if (!packet->data || (packet->data_len < RADIUS_HDR_LEN) || + (packet->offset < 0)) { + fr_strerror_printf("ERROR: You must call rad_encode() before rad_sign()"); + return -1; + } + +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 uses the authenticator field for matching + * requests to responses, and does not otherwise verify + * it. + */ + if (packet->radiusv11) { + return 0; + } +#endif + + /* + * It wasn't assigned an Id, this is bad! + */ + if (packet->id < 0) { + fr_strerror_printf("ERROR: RADIUS packets must be assigned an Id"); + return -1; + } + + /* + * Set up the authentication vector with zero, or with + * the original vector, prior to signing. + */ + switch (packet->code) { + case PW_CODE_ACCOUNTING_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: + case PW_CODE_COA_REQUEST: + memset(packet->vector, 0, AUTH_VECTOR_LEN); + break; + + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + case PW_CODE_ACCOUNTING_RESPONSE: + case PW_CODE_DISCONNECT_ACK: + case PW_CODE_DISCONNECT_NAK: + case PW_CODE_COA_ACK: + case PW_CODE_COA_NAK: + if (!original) { + fr_strerror_printf("ERROR: Cannot sign response packet without a request packet"); + return -1; + } + memcpy(packet->vector, original->vector, AUTH_VECTOR_LEN); + break; + + case PW_CODE_ACCESS_REQUEST: + case PW_CODE_STATUS_SERVER: + default: + break; /* packet->vector is already random bytes */ + } + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) rad_print_hex(packet); +#endif + +#ifndef WITH_RADIUSV11_ONLY + /* + * If there's a Message-Authenticator, update it + * now. + */ + if ((packet->offset > 0) && ((size_t) (packet->offset + 18) <= packet->data_len)) { + uint8_t calc_auth_vector[AUTH_VECTOR_LEN]; + + switch (packet->code) { + case PW_CODE_ACCOUNTING_RESPONSE: + if (original && original->code == PW_CODE_STATUS_SERVER) { + goto do_ack; + } + /* FALL-THROUGH */ + + case PW_CODE_ACCOUNTING_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: + case PW_CODE_DISCONNECT_ACK: + case PW_CODE_DISCONNECT_NAK: + case PW_CODE_COA_REQUEST: + case PW_CODE_COA_ACK: + case PW_CODE_COA_NAK: + memset(hdr->vector, 0, AUTH_VECTOR_LEN); + break; + + do_ack: + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + memcpy(hdr->vector, original->vector, AUTH_VECTOR_LEN); + break; + + default: + break; + } + + /* + * Set the authentication vector to zero, + * calculate the HMAC, and put it + * into the Message-Authenticator + * attribute. + */ + fr_hmac_md5(calc_auth_vector, packet->data, packet->data_len, + (uint8_t const *) secret, strlen(secret)); + memcpy(packet->data + packet->offset + 2, + calc_auth_vector, AUTH_VECTOR_LEN); + } +#endif /* WITH_RADIUSV11_ONLY */ + + /* + * Copy the request authenticator over to the packet. + */ + memcpy(hdr->vector, packet->vector, AUTH_VECTOR_LEN); + +#ifndef WITH_RADIUSV11_ONLY + /* + * Switch over the packet code, deciding how to + * sign the packet. + */ + switch (packet->code) { + /* + * Request packets are not signed, but + * have a random authentication vector. + */ + case PW_CODE_ACCESS_REQUEST: + case PW_CODE_STATUS_SERVER: + break; + + /* + * Reply packets are signed with the + * authentication vector of the request. + */ + default: + { + uint8_t digest[16]; + + FR_MD5_CTX context; + fr_md5_init(&context); + fr_md5_update(&context, packet->data, packet->data_len); + fr_md5_update(&context, (uint8_t const *) secret, + strlen(secret)); + fr_md5_final(digest, &context); + fr_md5_destroy(&context); + + memcpy(hdr->vector, digest, AUTH_VECTOR_LEN); + memcpy(packet->vector, digest, AUTH_VECTOR_LEN); + break; + } + }/* switch over packet codes */ +#endif /* WITH_RADIUSV11_ONLY */ + + return 0; +} + +/** Reply to the request + * + * Also attach reply attribute value pairs and any user message provided. + */ +int rad_send(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + char const *secret) +{ + /* + * Maybe it's a fake packet. Don't send it. + */ + if (!packet || (packet->sockfd < 0)) { + return 0; + } + + /* + * First time through, allocate room for the packet + */ + if (!packet->data) { + /* + * Encode the packet. + */ + if (rad_encode(packet, original, secret) < 0) { + return -1; + } + + /* + * Re-sign it, including updating the + * Message-Authenticator. + */ + if (rad_sign(packet, original, secret) < 0) { + return -1; + } + + /* + * If packet->data points to data, then we print out + * the VP list again only for debugging. + */ + } + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) rad_print_hex(packet); +#endif + +#ifdef WITH_TCP + /* + * If the socket is TCP, call write(). Calling sendto() + * is allowed on some platforms, but it's not nice. Even + * worse, if UDPFROMTO is defined, we *can't* use it on + * TCP sockets. So... just call write(). + */ + if (packet->proto == IPPROTO_TCP) { + ssize_t rcode; + + rcode = write(packet->sockfd, packet->data, packet->data_len); + if (rcode >= 0) return rcode; + + fr_strerror_printf("sendto failed: %s", fr_syserror(errno)); + return -1; + } +#endif + + /* + * And send it on it's way. + */ + return rad_sendto(packet->sockfd, packet->data, packet->data_len, 0, + &packet->src_ipaddr, packet->src_port, + &packet->dst_ipaddr, packet->dst_port); +} + +/** Do a comparison of two authentication digests by comparing the FULL digest + * + * Otherwise, the server can be subject to timing attacks that allow attackers + * find a valid message authenticator. + * + * http://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf + */ +int rad_digest_cmp(uint8_t const *a, uint8_t const *b, size_t length) +{ + int result = 0; + size_t i; + + for (i = 0; i < length; i++) { + result |= a[i] ^ b[i]; + } + + return result; /* 0 is OK, !0 is !OK, just like memcmp */ +} + +#ifndef WITH_RADIUSV11_ONLY +/** Validates the requesting client NAS + * + * Calculates the request Authenticator based on the clients private key. + */ +static int calc_acctdigest(RADIUS_PACKET *packet, char const *secret) +{ + uint8_t digest[AUTH_VECTOR_LEN]; + FR_MD5_CTX context; + + /* + * Zero out the auth_vector in the received packet. + * Then append the shared secret to the received packet, + * and calculate the MD5 sum. This must be the same + * as the original MD5 sum (packet->vector). + */ + memset(packet->data + 4, 0, AUTH_VECTOR_LEN); + + /* + * MD5(packet + secret); + */ + fr_md5_init(&context); + fr_md5_update(&context, packet->data, packet->data_len); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); + fr_md5_final(digest, &context); + fr_md5_destroy(&context); + + /* + * Return 0 if OK, 2 if not OK. + */ + if (rad_digest_cmp(digest, packet->vector, AUTH_VECTOR_LEN) != 0) return 2; + return 0; +} + + +/** Validates the requesting client NAS + * + * Calculates the response Authenticator based on the clients + * private key. + */ +static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original, + char const *secret) +{ + uint8_t calc_digest[AUTH_VECTOR_LEN]; + FR_MD5_CTX context; + + /* + * Very bad! + */ + if (original == NULL) { + return 3; + } + + /* + * Copy the original vector in place. + */ + memcpy(packet->data + 4, original->vector, AUTH_VECTOR_LEN); + + /* + * MD5(packet + secret); + */ + fr_md5_init(&context); + fr_md5_update(&context, packet->data, packet->data_len); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); + fr_md5_final(calc_digest, &context); + fr_md5_destroy(&context); + + /* + * Copy the packet's vector back to the packet. + */ + memcpy(packet->data + 4, packet->vector, AUTH_VECTOR_LEN); + + /* + * Return 0 if OK, 2 if not OK. + */ + if (rad_digest_cmp(packet->vector, calc_digest, AUTH_VECTOR_LEN) != 0) return 2; + return 0; +} +#endif /* WITH_RADIUSV11_ONLY */ + +/** Check if a set of RADIUS formatted TLVs are OK + * + */ +int rad_tlv_ok(uint8_t const *data, size_t length, + size_t dv_type, size_t dv_length) +{ + uint8_t const *end = data + length; + + VP_TRACE("checking TLV %u/%u\n", (unsigned int) dv_type, (unsigned int) dv_length); + + VP_HEXDUMP("tlv_ok", data, length); + + if ((dv_length > 2) || (dv_type == 0) || (dv_type > 4)) { + fr_strerror_printf("rad_tlv_ok: Invalid arguments"); + return -1; + } + + while (data < end) { + size_t attrlen; + + if ((data + dv_type + dv_length) > end) { + fr_strerror_printf("Attribute header overflow"); + return -1; + } + + switch (dv_type) { + case 4: + if ((data[0] == 0) && (data[1] == 0) && + (data[2] == 0) && (data[3] == 0)) { + zero: + fr_strerror_printf("Invalid attribute 0"); + return -1; + } + + if (data[0] != 0) { + fr_strerror_printf("Invalid attribute > 2^24"); + return -1; + } + break; + + case 2: + if ((data[0] == 0) && (data[1] == 0)) goto zero; + break; + + case 1: + /* + * Zero is allowed, because the Colubris + * people are dumb and use it. + */ + break; + + default: + fr_strerror_printf("Internal sanity check failed"); + return -1; + } + + switch (dv_length) { + case 0: + return 0; + + case 2: + if (data[dv_type] != 0) { + fr_strerror_printf("Attribute is longer than 256 octets"); + return -1; + } + /* FALL-THROUGH */ + case 1: + attrlen = data[dv_type + dv_length - 1]; + break; + + + default: + fr_strerror_printf("Internal sanity check failed"); + return -1; + } + + if (attrlen < (dv_type + dv_length)) { + fr_strerror_printf("Attribute header has invalid length"); + return -1; + } + + if (attrlen > length) { + fr_strerror_printf("Attribute overflows container"); + return -1; + } + + data += attrlen; + length -= attrlen; + } + + return 0; +} + + +/** See if the data pointed to by PTR is a valid RADIUS packet. + * + * Packet is not 'const * const' because we may update data_len, if there's more data + * in the UDP packet than in the RADIUS packet. + * + * @param packet to check + * @param flags to control decoding + * @param reason if not NULL, will have the failure reason written to where it points. + * @return bool, true on success, false on failure. + */ +bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) +{ + uint8_t *attr; + size_t totallen; + int count; + radius_packet_t *hdr; + char host_ipaddr[128]; +#ifndef WITH_RADIUSV11_ONLY + bool require_ma = false; + bool seen_ma = false; + bool eap = false; + bool non_eap = false; +#endif + uint32_t num_attributes; + decode_fail_t failure = DECODE_FAIL_NONE; + + /* + * Check for packets smaller than the packet header. + * + * RFC 2865, Section 3., subsection 'length' says: + * + * "The minimum length is 20 ..." + */ + if (packet->data_len < RADIUS_HDR_LEN) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: too short (received %zu < minimum %d)", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + packet->data_len, RADIUS_HDR_LEN); + failure = DECODE_FAIL_MIN_LENGTH_PACKET; + goto finish; + } + + + /* + * Check for packets with mismatched size. + * i.e. We've received 128 bytes, and the packet header + * says it's 256 bytes long. + */ + totallen = (packet->data[2] << 8) | packet->data[3]; + hdr = (radius_packet_t *)packet->data; + + /* + * Code of 0 is not understood. + * Code of 16 or greate is not understood. + */ + if ((hdr->code == 0) || + (hdr->code >= FR_MAX_PACKET_CODE)) { + FR_DEBUG_STRERROR_PRINTF("Bad RADIUS packet from host %s: unknown packet code %d", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + hdr->code); + failure = DECODE_FAIL_UNKNOWN_PACKET_CODE; + goto finish; + } + + /* + * Message-Authenticator is required in Status-Server + * packets, otherwise they can be trivially forged. + */ + if (hdr->code == PW_CODE_STATUS_SERVER) require_ma = true; + + /* + * It's also required if the caller asks for it. + */ + if (flags) require_ma = true; + + /* + * Repeat the length checks. This time, instead of + * looking at the data we received, look at the value + * of the 'length' field inside of the packet. + * + * Check for packets smaller than the packet header. + * + * RFC 2865, Section 3., subsection 'length' says: + * + * "The minimum length is 20 ..." + */ + if (totallen < RADIUS_HDR_LEN) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: too short (length %zu < minimum %d)", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + totallen, RADIUS_HDR_LEN); + failure = DECODE_FAIL_MIN_LENGTH_FIELD; + goto finish; + } + + /* + * And again, for the value of the 'length' field. + * + * RFC 2865, Section 3., subsection 'length' says: + * + * " ... and maximum length is 4096." + * + * HOWEVER. This requirement is for the network layer. + * If the code gets here, we assume that a well-formed + * packet is an OK packet. + * + * We allow both the UDP data length, and the RADIUS + * "length" field to contain up to 64K of data. + */ + + /* + * RFC 2865, Section 3., subsection 'length' says: + * + * "If the packet is shorter than the Length field + * indicates, it MUST be silently discarded." + * + * i.e. No response to the NAS. + */ + if (packet->data_len < totallen) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: received %zu octets, packet length says %zu", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + packet->data_len, totallen); + failure = DECODE_FAIL_MIN_LENGTH_MISMATCH; + goto finish; + } + + /* + * RFC 2865, Section 3., subsection 'length' says: + * + * "Octets outside the range of the Length field MUST be + * treated as padding and ignored on reception." + */ + if (packet->data_len > totallen) { + /* + * We're shortening the packet below, but just + * to be paranoid, zero out the extra data. + */ + memset(packet->data + totallen, 0, packet->data_len - totallen); + packet->data_len = totallen; + } + + /* + * Walk through the packet's attributes, ensuring that + * they add up EXACTLY to the size of the packet. + * + * If they don't, then the attributes either under-fill + * or over-fill the packet. Any parsing of the packet + * is impossible, and will result in unknown side effects. + * + * This would ONLY happen with buggy RADIUS implementations, + * or with an intentional attack. Either way, we do NOT want + * to be vulnerable to this problem. + */ + attr = hdr->data; + count = totallen - RADIUS_HDR_LEN; + num_attributes = 0; + + while (count > 0) { + /* + * We need at least 2 bytes to check the + * attribute header. + */ + if (count < 2) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: attribute header overflows the packet", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr))); + failure = DECODE_FAIL_HEADER_OVERFLOW; + goto finish; + } + + /* + * Attribute number zero is NOT defined. + */ + if (attr[0] == 0) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: Invalid attribute 0", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr))); + failure = DECODE_FAIL_INVALID_ATTRIBUTE; + goto finish; + } + + /* + * Attributes are at LEAST as long as the ID & length + * fields. Anything shorter is an invalid attribute. + */ + if (attr[1] < 2) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: attribute %u too short", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + attr[0]); + failure = DECODE_FAIL_ATTRIBUTE_TOO_SHORT; + goto finish; + } + + /* + * If there are fewer bytes in the packet than in the + * attribute, it's a bad packet. + */ + if (count < attr[1]) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: attribute %u data overflows the packet", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + attr[0]); + failure = DECODE_FAIL_ATTRIBUTE_OVERFLOW; + goto finish; + } + +#ifndef WITH_RADIUSV11_ONLY + /* + * Sanity check the attributes for length. + */ + switch (attr[0]) { + default: /* don't do anything by default */ + break; + + /* + * If there's an EAP-Message, we require + * a Message-Authenticator. + */ + case PW_EAP_MESSAGE: + require_ma = true; + eap = true; + break; + + case PW_USER_PASSWORD: + case PW_CHAP_PASSWORD: + case PW_ARAP_PASSWORD: + non_eap = true; + break; + + case PW_MESSAGE_AUTHENTICATOR: +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 does not encode or verify Message-Authenticator. + */ + if (packet->radiusv11) break; +#endif + + if (attr[1] != 2 + AUTH_VECTOR_LEN) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: Message-Authenticator has invalid length %d", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + attr[1] - 2); + failure = DECODE_FAIL_MA_INVALID_LENGTH; + goto finish; + } + seen_ma = true; + break; + } +#endif + + /* + * FIXME: Look up the base 255 attributes in the + * dictionary, and switch over their type. For + * integer/date/ip, the attribute length SHOULD + * be 6. + */ + count -= attr[1]; /* grab the attribute length */ + attr += attr[1]; + num_attributes++; /* seen one more attribute */ + } + + /* + * If the attributes add up to a packet, it's allowed. + * + * If not, we complain, and throw the packet away. + */ + if (count != 0) { + FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: packet attributes do NOT exactly fill the packet", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr))); + failure = DECODE_FAIL_ATTRIBUTE_UNDERFLOW; + goto finish; + } + + /* + * If we're configured to look for a maximum number of + * attributes, and we've seen more than that maximum, + * then throw the packet away, as a possible DoS. + */ + if ((fr_max_attributes > 0) && + (num_attributes > fr_max_attributes)) { + FR_DEBUG_STRERROR_PRINTF("Possible DoS attack from host %s: Too many attributes in request (received %d, max %d are allowed).", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + num_attributes, fr_max_attributes); + failure = DECODE_FAIL_TOO_MANY_ATTRIBUTES; + goto finish; + } + + /* + * http://www.freeradius.org/rfc/rfc2869.html#EAP-Message + * + * A packet with an EAP-Message attribute MUST also have + * a Message-Authenticator attribute. + * + * A Message-Authenticator all by itself is OK, though. + * + * Similarly, Status-Server packets MUST contain + * Message-Authenticator attributes. + */ + if (require_ma && +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 does not encode or verify Message-Authenticator. + */ + !packet->radiusv11 && +#endif + !seen_ma) { + FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr))); + failure = DECODE_FAIL_MA_MISSING; + goto finish; + } + +#ifndef WITH_RADIUSV11_ONLY + if (eap && non_eap) { + FR_DEBUG_STRERROR_PRINTF("Bad packet from host %s: Packet contains EAP-Message and non-EAP authentication attribute", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr))); + failure = DECODE_FAIL_TOO_MANY_AUTH; + goto finish; + } +#endif + + /* + * Fill RADIUS header fields + */ + packet->code = hdr->code; + packet->id = hdr->id; +#ifdef WITH_RADIUSV11 + if (packet->radiusv11) { + uint32_t id; + + memcpy(&id, hdr->vector, sizeof(id)); + packet->id = ntohl(id); + } +#endif + memcpy(packet->vector, hdr->vector, AUTH_VECTOR_LEN); + + + finish: + + if (reason) { + *reason = failure; + } + return (failure == DECODE_FAIL_NONE); +} + + +/** Receive UDP client requests, and fill in the basics of a RADIUS_PACKET structure + * + */ +RADIUS_PACKET *rad_recv(TALLOC_CTX *ctx, int fd, int flags) +{ + int sock_flags = 0; + ssize_t data_len; + RADIUS_PACKET *packet; + + /* + * Allocate the new request data structure + */ + packet = rad_alloc(ctx, false); + if (!packet) { + fr_strerror_printf("out of memory"); + return NULL; + } + + if (flags & 0x02) { + sock_flags = MSG_PEEK; + flags &= ~0x02; + } + + data_len = rad_recvfrom(fd, packet, sock_flags, + &packet->src_ipaddr, &packet->src_port, + &packet->dst_ipaddr, &packet->dst_port); + + /* + * Check for socket errors. + */ + if (data_len < 0) { + FR_DEBUG_STRERROR_PRINTF("Error receiving packet: %s", fr_syserror(errno)); + /* packet->data is NULL */ + rad_free(&packet); + return NULL; + } + + /* + * No data read from the network. + */ + if (data_len == 0) { + rad_free(&packet); + return NULL; + } + + /* + * See if it's a well-formed RADIUS packet. + */ + if (!rad_packet_ok(packet, flags, NULL)) { + rad_free(&packet); + return NULL; + } + + /* + * Remember which socket we read the packet from. + */ + packet->sockfd = fd; + + /* + * FIXME: Do even more filtering by only permitting + * certain IP's. The problem is that we don't know + * how to do this properly for all possible clients... + */ + + /* + * Explicitely set the VP list to empty. + */ + packet->vps = NULL; + +#ifndef NDEBUG + if ((fr_debug_lvl > 3) && fr_log_fp) rad_print_hex(packet); +#endif + + return packet; +} + + +/** Verify the Request/Response Authenticator (and Message-Authenticator if present) of a packet + * + */ +int rad_verify(RADIUS_PACKET *packet, RADIUSV11_UNUSED RADIUS_PACKET *original, RADIUSV11_UNUSED char const *secret) +{ + uint8_t *ptr; + int length; + int attrlen; +#ifndef WITH_RADIUSV11_ONLY + int rcode; +#endif + char buffer[32]; + + if (!packet || !packet->data) return -1; + +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 uses the authenticator field for matching + * requests to responses, and does not otherwise verify + * it. + */ + if (packet->radiusv11) { + return 0; + } +#endif + + /* + * Before we allocate memory for the attributes, do more + * sanity checking. + */ + ptr = packet->data + RADIUS_HDR_LEN; + length = packet->data_len - RADIUS_HDR_LEN; + while (length > 0) { +#ifndef WITH_RADIUSV11_ONLY + uint8_t msg_auth_vector[AUTH_VECTOR_LEN]; + uint8_t calc_auth_vector[AUTH_VECTOR_LEN]; +#endif + + attrlen = ptr[1]; + +#ifndef WITH_RADIUSV11_ONLY + switch (ptr[0]) { + default: /* don't do anything. */ + break; + + /* + * Note that more than one Message-Authenticator + * attribute is invalid. + */ + case PW_MESSAGE_AUTHENTICATOR: +#ifdef WITH_RADIUSV11 + /* + * Ignore Message-Authenticator for RADIUSV11 packets. + */ + if (packet->radiusv11) break; +#endif + + memcpy(msg_auth_vector, &ptr[2], sizeof(msg_auth_vector)); + memset(&ptr[2], 0, AUTH_VECTOR_LEN); + + switch (packet->code) { + default: + break; + + case PW_CODE_ACCOUNTING_RESPONSE: + if (original && + (original->code == PW_CODE_STATUS_SERVER)) { + goto do_ack; + } + /* FALL-THROUGH */ + + case PW_CODE_ACCOUNTING_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: + case PW_CODE_COA_REQUEST: + memset(packet->data + 4, 0, AUTH_VECTOR_LEN); + break; + + do_ack: + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + case PW_CODE_DISCONNECT_ACK: + case PW_CODE_DISCONNECT_NAK: + case PW_CODE_COA_ACK: + case PW_CODE_COA_NAK: + if (!original) { + fr_strerror_printf("Cannot validate Message-Authenticator in response " + "packet without a request packet"); + return -1; + } + memcpy(packet->data + 4, original->vector, AUTH_VECTOR_LEN); + break; + } + + fr_hmac_md5(calc_auth_vector, packet->data, packet->data_len, + (uint8_t const *) secret, strlen(secret)); + if (rad_digest_cmp(calc_auth_vector, msg_auth_vector, + sizeof(calc_auth_vector)) != 0) { + fr_strerror_printf("Received packet from %s with invalid Message-Authenticator! " + "(Shared secret is incorrect.)", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer))); + /* Silently drop packet, according to RFC 3579 */ + return -1; + } /* else the message authenticator was good */ + + /* + * Reinitialize Authenticators. + */ + memcpy(&ptr[2], msg_auth_vector, AUTH_VECTOR_LEN); + memcpy(packet->data + 4, packet->vector, AUTH_VECTOR_LEN); + break; + } /* switch over the attributes */ +#endif /* WITH_RADIUSV11_ONLY */ + + ptr += attrlen; + length -= attrlen; + } /* loop over the packet, sanity checking the attributes */ + + /* + * It looks like a RADIUS packet, but we don't know what it is + * so can't validate the authenticators. + */ + if ((packet->code == 0) || (packet->code >= FR_MAX_PACKET_CODE)) { + fr_strerror_printf("Received Unknown packet code %d " + "from client %s port %d: Cannot validate Request/Response Authenticator.", + packet->code, + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer)), + packet->src_port); + return -1; + } + +#ifndef WITH_RADIUSV11_ONLY +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 uses the authenticator field for matching + * requests to responses, and does not otherwise verify + * it. + */ + if (packet->radiusv11) return 0; +#endif + + /* + * Calculate and/or verify Request or Response Authenticator. + */ + switch (packet->code) { + case PW_CODE_ACCESS_REQUEST: + case PW_CODE_STATUS_SERVER: + /* + * The authentication vector is random + * nonsense, invented by the client. + */ + break; + + case PW_CODE_COA_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: + case PW_CODE_ACCOUNTING_REQUEST: + if (calc_acctdigest(packet, secret) > 1) { + fr_strerror_printf("Received %s packet " + "from client %s with invalid Request Authenticator! " + "(Shared secret is incorrect.)", + fr_packet_codes[packet->code], + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer))); + return -1; + } + break; + + /* Verify the reply digest */ + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + case PW_CODE_ACCOUNTING_RESPONSE: + case PW_CODE_DISCONNECT_ACK: + case PW_CODE_DISCONNECT_NAK: + case PW_CODE_COA_ACK: + case PW_CODE_COA_NAK: + rcode = calc_replydigest(packet, original, secret); + if (rcode > 1) { + fr_strerror_printf("Received %s packet " + "from home server %s port %d with invalid Response Authenticator! " + "(Shared secret is incorrect.)", + fr_packet_codes[packet->code], + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer)), + packet->src_port); + return -1; + } + break; + + default: + fr_strerror_printf("Received Unknown packet code %d " + "from client %s port %d: Cannot validate Request/Response Authenticator", + packet->code, + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer)), + packet->src_port); + return -1; + } +#endif + + return 0; +} + + +/** Convert one or more NAS-Filter-Rule attributes to one or more + * attributes. + * + */ +static ssize_t data2vp_nas_filter_rule(TALLOC_CTX *ctx, + DICT_ATTR const *da, uint8_t const *start, + size_t const packetlen, VALUE_PAIR **pvp) +{ + uint8_t const *p = start; + uint8_t const *attr = start; + uint8_t const *end = start + packetlen; + uint8_t const *attr_end; + uint8_t *q; + VALUE_PAIR *vp; + uint8_t buffer[253]; + + q = buffer; + + /* + * The packet has already been sanity checked, so we + * don't care about walking off of the end of it. + */ + while (attr < end) { + if ((attr + 2) > end) { + fr_strerror_printf("decode NAS-Filter-Rule: Failure (1) to call rad_packet_ok"); + return -1; + } + + if (attr[1] < 2) { + fr_strerror_printf("decode NAS-Filter-Rule: Failure (2) to call rad_packet_ok"); + return -1; + } + if (attr[0] != PW_NAS_FILTER_RULE) break; + + /* + * Now decode one, or part of one rule. + */ + p = attr + 2; + attr_end = attr + attr[1]; + + if (attr_end > end) { + fr_strerror_printf("decode NAS-Filter-Rule: Failure (3) to call rad_packet_ok"); + return -1; + } + + /* + * Coalesce data until the zero byte. + */ + while (p < attr_end) { + /* + * Once we hit the zero byte, create the + * VP, skip the zero byte, and reset the + * counters. + */ + if (*p == 0) { + /* + * Discard consecutive zeroes. + */ + if (q > buffer) { + vp = fr_pair_afrom_da(ctx, da); + if (!vp) { + fr_strerror_printf("decode NAS-Filter-Rule: Out of memory"); + return -1; + } + + fr_pair_value_bstrncpy(vp, buffer, q - buffer); + + *pvp = vp; + pvp = &(vp->next); + q = buffer; + } + + p++; + continue; + } + *(q++) = *(p++); + + /* + * Not much reason to have rules which + * are too long. + */ + if ((size_t) (q - buffer) > sizeof(buffer)) { + fr_strerror_printf("decode NAS-Filter-Rule: decoded attribute is too long"); + return -1; + } + } + + /* + * Done this attribute. There MAY be things left + * in the buffer. + */ + attr = attr_end; + } + + if (q == buffer) return attr + attr[2] - start; + + vp = fr_pair_afrom_da(ctx, da); + if (!vp) { + fr_strerror_printf("decode NAS-Filter-Rule: Out of memory"); + return -1; + } + + fr_pair_value_bstrncpy(vp, buffer, q - buffer); + + *pvp = vp; + + return p - start; +} + +/** Convert a "concatenated" attribute to one long VP + * + */ +static ssize_t data2vp_concat(TALLOC_CTX *ctx, + DICT_ATTR const *da, uint8_t const *start, + size_t const packetlen, VALUE_PAIR **pvp) +{ + size_t total; + uint8_t attr; + uint8_t const *ptr = start; + uint8_t const *end = start + packetlen; + uint8_t *p; + VALUE_PAIR *vp; + + total = 0; + attr = ptr[0]; + + /* + * The packet has already been sanity checked, so we + * don't care about walking off of the end of it. + */ + while (ptr < end) { + if (ptr[1] < 2) return -1; + if ((ptr + ptr[1]) > end) return -1; + + total += ptr[1] - 2; + + ptr += ptr[1]; + + if (ptr == end) break; + + /* + * Attributes MUST be consecutive. + */ + if (ptr[0] != attr) break; + } + + end = ptr; + + vp = fr_pair_afrom_da(ctx, da); + if (!vp) return -1; + + vp->vp_length = total; + vp->vp_octets = p = talloc_array(vp, uint8_t, vp->vp_length); + if (!p) { + fr_pair_list_free(&vp); + return -1; + } + + total = 0; + ptr = start; + while (ptr < end) { + memcpy(p, ptr + 2, ptr[1] - 2); + p += ptr[1] - 2; + total += ptr[1] - 2; + ptr += ptr[1]; + } + + *pvp = vp; + + return ptr - start; +} + + +/** Convert TLVs to one or more VPs + * + */ +ssize_t rad_data2vp_tlvs(TALLOC_CTX *ctx, + RADIUS_PACKET *packet, RADIUS_PACKET const *original, + char const *secret, DICT_ATTR const *da, + uint8_t const *start, size_t length, + VALUE_PAIR **pvp) +{ + uint8_t const *data = start; + DICT_ATTR const *child; + VALUE_PAIR *head, **tail; + + if (length < 3) return -1; /* type, length, value */ + + VP_HEXDUMP("tlvs", data, length); + + if (rad_tlv_ok(data, length, 1, 1) < 0) return -1; + + head = NULL; + tail = &head; + + while (data < (start + length)) { + ssize_t tlv_len; + + child = dict_attrbyparent(da, data[0], da->vendor); + if (!child) { + unsigned int my_attr, my_vendor; + + VP_TRACE("Failed to find child %u of TLV %s\n", + data[0], da->name); + + /* + * Get child attr/vendor so that + * we can call unknown attr. + */ + my_attr = data[0]; + my_vendor = da->vendor; + + if (!dict_attr_child(da, &my_attr, &my_vendor)) { + fr_pair_list_free(&head); + return -1; + } + + child = dict_unknown_afrom_fields(ctx, my_attr, my_vendor); + if (!child) { + fr_pair_list_free(&head); + return -1; + } + } + + tlv_len = data2vp(ctx, packet, original, secret, child, + data + 2, data[1] - 2, data[1] - 2, tail); + if (tlv_len < 0) { + fr_pair_list_free(&head); + return -1; + } + if (*tail) tail = &((*tail)->next); + data += data[1]; + } + + *pvp = head; + return length; +} + +/** Convert a top-level VSA to a VP. + * + * "length" can be LONGER than just this sub-vsa. + */ +static ssize_t data2vp_vsa(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + RADIUS_PACKET const *original, + char const *secret, DICT_VENDOR *dv, + uint8_t const *data, size_t length, + VALUE_PAIR **pvp) +{ + unsigned int attribute; + ssize_t attrlen, my_len; + DICT_ATTR const *da; + + VP_TRACE("data2vp_vsa: length %u\n", (unsigned int) length); + +#ifndef NDEBUG + if (length <= (dv->type + dv->length)) { + fr_strerror_printf("data2vp_vsa: Failure to call rad_tlv_ok"); + return -1; + } +#endif + + switch (dv->type) { + case 4: + /* data[0] must be zero */ + attribute = data[1] << 16; + attribute |= data[2] << 8; + attribute |= data[3]; + break; + + case 2: + attribute = data[0] << 8; + attribute |= data[1]; + break; + + case 1: + attribute = data[0]; + break; + + default: + fr_strerror_printf("data2vp_vsa: Internal sanity check failed"); + return -1; + } + + switch (dv->length) { + case 2: + /* data[dv->type] must be zero, from rad_tlv_ok() */ + attrlen = data[dv->type + 1]; + break; + + case 1: + attrlen = data[dv->type]; + break; + + case 0: + attrlen = length; + break; + + default: + fr_strerror_printf("data2vp_vsa: Internal sanity check failed"); + return -1; + } + + /* + * See if the VSA is known. + */ + da = dict_attrbyvalue(attribute, dv->vendorpec); + if (!da) da = dict_unknown_afrom_fields(ctx, attribute, dv->vendorpec); + if (!da) return -1; + + my_len = data2vp(ctx, packet, original, secret, da, + data + dv->type + dv->length, + attrlen - (dv->type + dv->length), + attrlen - (dv->type + dv->length), + pvp); + if (my_len < 0) return my_len; + + return attrlen; +} + + +/** Convert a fragmented extended attr to a VP + * + * Format is: + * + * attr + * length + * extended-attr + * flag + * data... + * + * But for the first fragment, we get passed a pointer to the "extended-attr" + */ +static ssize_t data2vp_extended(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + RADIUS_PACKET const *original, + char const *secret, DICT_ATTR const *da, + uint8_t const *data, + size_t attrlen, size_t packetlen, + VALUE_PAIR **pvp) +{ + ssize_t rcode; + size_t ext_len; + bool more; + uint8_t *head, *tail; + uint8_t const *attr, *end; + DICT_ATTR const *child; + + /* + * data = Ext-Attr Flag ... + */ + + /* + * Not enough room for Ext-Attr + Flag + data, it's a bad + * attribute. + */ + if (attrlen < 3) { + raw: + /* + * It's not an Extended attribute, it's unknown... + */ + child = dict_unknown_afrom_fields(ctx, (da->vendor/ FR_MAX_VENDOR) & 0xff, 0); + if (!child) { + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } + + rcode = data2vp(ctx, packet, original, secret, child, + data, attrlen, attrlen, pvp); + if (rcode < 0) return rcode; + return attrlen; + } + + /* + * No continued data, just decode the attribute in place. + */ + if ((data[1] & 0x80) == 0) { + rcode = data2vp(ctx, packet, original, secret, da, + data + 2, attrlen - 2, attrlen - 2, + pvp); + + if ((rcode < 0) || (((size_t) rcode + 2) != attrlen)) goto raw; /* didn't decode all of the data */ + return attrlen; + } + + /* + * It's continued, but there are no subsequent fragments, + * it's bad. + */ + if (attrlen >= packetlen) goto raw; + + /* + * Calculate the length of all of the fragments. For + * now, they MUST be contiguous in the packet, and they + * MUST be all of the same Type and Ext-Type + * + * We skip the first fragment, which doesn't have a + * RADIUS attribute header. + */ + ext_len = attrlen - 2; + attr = data + attrlen; + end = data + packetlen; + + while (attr < end) { + /* + * Not enough room for Attr + length + Ext-Attr + * continuation, it's bad. + */ + if ((end - attr) < 4) goto raw; + + if (attr[1] < 4) goto raw; + + /* + * If the attribute overflows the packet, it's + * bad. + */ + if ((attr + attr[1]) > end) goto raw; + + if (attr[0] != ((da->vendor / FR_MAX_VENDOR) & 0xff)) goto raw; /* not the same Extended-Attribute-X */ + + if (attr[2] != data[0]) goto raw; /* Not the same Ext-Attr */ + + /* + * Check the continuation flag. + */ + more = ((attr[2] & 0x80) != 0); + + /* + * Or, there's no more data, in which case we + * shorten "end" to finish at this attribute. + */ + if (!more) end = attr + attr[1]; + + /* + * There's more data, but we're at the end of the + * packet. The attribute is malformed! + */ + if (more && ((attr + attr[1]) == end)) goto raw; + + /* + * Add in the length of the data we need to + * concatenate together. + */ + ext_len += attr[1] - 4; + + /* + * Go to the next attribute, and stop if there's + * no more. + */ + attr += attr[1]; + if (!more) break; + } + + if (!ext_len) goto raw; + + head = tail = malloc(ext_len); + if (!head) goto raw; + + /* + * Copy the data over, this time trusting the attribute + * contents. + */ + attr = data; + memcpy(tail, attr + 2, attrlen - 2); + tail += attrlen - 2; + attr += attrlen; + + while (attr < end) { + if (attr[1] > 4) memcpy(tail, attr + 4, attr[1] - 4); + tail += attr[1] - 4; + attr += attr[1]; /* skip VID+WiMax header */ + } + + VP_HEXDUMP("long-extended fragments", head, ext_len); + + rcode = data2vp(ctx, packet, original, secret, da, + head, ext_len, ext_len, pvp); + free(head); + if (rcode < 0) goto raw; + + return end - data; +} + +/** Convert a Vendor-Specific WIMAX to VPs + * + * @note Called ONLY for Vendor-Specific + */ +static ssize_t data2vp_wimax(TALLOC_CTX *ctx, + RADIUS_PACKET *packet, RADIUS_PACKET const *original, + char const *secret, uint32_t vendor, + uint8_t const *data, + size_t attrlen, size_t packetlen, + VALUE_PAIR **pvp) +{ + ssize_t rcode; + size_t wimax_len; + bool more; + uint8_t *head, *tail; + uint8_t const *attr, *end; + DICT_ATTR const *child; + + /* + * data = VID VID VID VID WiMAX-Attr WimAX-Len Continuation ... + */ + + /* + * Not enough room for WiMAX Vendor + Wimax attr + length + * + continuation, it's a bad attribute. + */ + if (attrlen < 8) { + raw: + /* + * It's not a Vendor-Specific, it's unknown... + */ + child = dict_unknown_afrom_fields(ctx, PW_VENDOR_SPECIFIC, 0); + if (!child) { + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } + + rcode = data2vp(ctx, packet, original, secret, child, + data, attrlen, attrlen, pvp); + if (rcode < 0) return rcode; + return attrlen; + } + + if (data[5] < 3) goto raw; /* WiMAX-Length is too small */ + + child = dict_attrbyvalue(data[4], vendor); + if (!child) goto raw; + + /* + * No continued data, just decode the attribute in place. + */ + if ((data[6] & 0x80) == 0) { + if (((size_t) (data[5] + 4)) != attrlen) goto raw; /* WiMAX attribute doesn't fill Vendor-Specific */ + + rcode = data2vp(ctx, packet, original, secret, child, + data + 7, data[5] - 3, data[5] - 3, + pvp); + + if ((rcode < 0) || (((size_t) rcode + 7) != attrlen)) goto raw; /* didn't decode all of the data */ + return attrlen; + } + + /* + * Calculate the length of all of the fragments. For + * now, they MUST be contiguous in the packet, and they + * MUST be all of the same VSA, WiMAX, and WiMAX-attr. + * + * The first fragment doesn't have a RADIUS attribute + * header. + */ + wimax_len = 0; + attr = data + 4; + end = data + packetlen; + + while (attr < end) { + /* + * Not enough room for Attribute + length + + * continuation, it's bad. + */ + if ((end - attr) < 3) goto raw; + + /* + * Must have non-zero data in the attribute. + */ + if (attr[1] <= 3) goto raw; + + /* + * If the WiMAX attribute overflows the packet, + * it's bad. + */ + if ((attr + attr[1]) > end) goto raw; + + /* + * Check the continuation flag. + */ + more = ((attr[2] & 0x80) != 0); + + /* + * Or, there's no more data, in which case we + * shorten "end" to finish at this attribute. + */ + if (!more) end = attr + attr[1]; + + /* + * There's more data, but we're at the end of the + * packet. The attribute is malformed! + */ + if (more && ((attr + attr[1]) == end)) goto raw; + + /* + * Add in the length of the data we need to + * concatenate together. + */ + wimax_len += attr[1] - 3; + + /* + * Go to the next attribute, and stop if there's + * no more. + */ + attr += attr[1]; + if (!more) break; + + /* + * data = VID VID VID VID WiMAX-Attr WimAX-Len Continuation ... + * + * attr = Vendor-Specific VSA-Length VID VID VID VID WiMAX-Attr WimAX-Len Continuation ... + * + */ + + /* + * No room for Vendor-Specific + length + + * Vendor(4) + attr + length + continuation + data + */ + if ((end - attr) < 9) goto raw; + + if (attr[0] != PW_VENDOR_SPECIFIC) goto raw; + if (attr[1] < 9) goto raw; + if ((attr + attr[1]) > end) goto raw; + if (memcmp(data, attr + 2, 4) != 0) goto raw; /* not WiMAX Vendor ID */ + + if (attr[1] != (attr[7] + 6)) goto raw; /* WiMAX attr doesn't exactly fill the VSA */ + + if (data[4] != attr[6]) goto raw; /* different WiMAX attribute */ + + /* + * Skip over the Vendor-Specific header, and + * continue with the WiMAX attributes. + */ + attr += 6; + } + + /* + * No data in the WiMAX attribute, make a "raw" one. + */ + if (!wimax_len) goto raw; + + head = tail = malloc(wimax_len); + if (!head) return -1; + + /* + * Copy the data over, this time trusting the attribute + * contents. + */ + attr = data; + while (attr < end) { + memcpy(tail, attr + 4 + 3, attr[4 + 1] - 3); + tail += attr[4 + 1] - 3; + attr += 4 + attr[4 + 1]; /* skip VID+WiMax header */ + attr += 2; /* skip Vendor-Specific header */ + } + + VP_HEXDUMP("wimax fragments", head, wimax_len); + + rcode = data2vp(ctx, packet, original, secret, child, + head, wimax_len, wimax_len, pvp); + free(head); + if (rcode < 0) goto raw; + + return end - data; +} + + +/** Convert a top-level VSA to one or more VPs + * + */ +static ssize_t data2vp_vsas(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + RADIUS_PACKET const *original, + char const *secret, uint8_t const *data, + size_t attrlen, size_t packetlen, + VALUE_PAIR **pvp) +{ + size_t total; + ssize_t rcode; + uint32_t vendor; + DICT_VENDOR *dv; + VALUE_PAIR *head, **tail; + DICT_VENDOR my_dv; + + if (attrlen > packetlen) return -1; + if (attrlen < 5) return -1; /* vid, value */ + if (data[0] != 0) return -1; /* we require 24-bit VIDs */ + + VP_TRACE("data2vp_vsas\n"); + + memcpy(&vendor, data, 4); + vendor = ntohl(vendor); + dv = dict_vendorbyvalue(vendor); + if (!dv) { + /* + * RFC format is 1 octet type, 1 octet length + */ + if (rad_tlv_ok(data + 4, attrlen - 4, 1, 1) < 0) { + VP_TRACE("data2vp_vsas: unknown tlvs not OK: %s\n", fr_strerror()); + return -1; + } + + /* + * It's a known unknown. + */ + memset(&my_dv, 0, sizeof(my_dv)); + dv = &my_dv; + + /* + * Fill in the fields. Note that the name is empty! + */ + dv->vendorpec = vendor; + dv->type = 1; + dv->length = 1; + + goto create_attrs; + } + + /* + * WiMAX craziness + */ + if (dv->flags) { + rcode = data2vp_wimax(ctx, packet, original, secret, vendor, + data, attrlen, packetlen, pvp); + return rcode; + } + + /* + * VSAs should normally be in TLV format. + */ + if (rad_tlv_ok(data + 4, attrlen - 4, + dv->type, dv->length) < 0) { + VP_TRACE("data2vp_vsas: tlvs not OK: %s\n", fr_strerror()); + return -1; + } + + /* + * There may be more than one VSA in the + * Vendor-Specific. If so, loop over them all. + */ +create_attrs: + data += 4; + attrlen -= 4; + packetlen -= 4; + total = 4; + head = NULL; + tail = &head; + + while (attrlen > 0) { + ssize_t vsa_len; + + vsa_len = data2vp_vsa(ctx, packet, original, secret, dv, + data, attrlen, tail); + if (vsa_len < 0) { + fr_pair_list_free(&head); + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } + + /* + * Vendors can send zero-length VSAs. + */ + if (*tail) tail = &((*tail)->next); + + data += vsa_len; + attrlen -= vsa_len; + packetlen -= vsa_len; + total += vsa_len; + } + + *pvp = head; + return total; +} + +/** Create any kind of VP from the attribute contents + * + * "length" is AT LEAST the length of this attribute, as we + * expect the caller to have verified the data with + * rad_packet_ok(). "length" may be up to the length of the + * packet. + * + * @return -1 on error, or "length". + */ +ssize_t data2vp(TALLOC_CTX *ctx, + RADIUS_PACKET *packet, RADIUS_PACKET const *original, + char const *secret, + DICT_ATTR const *da, uint8_t const *start, + size_t const attrlen, size_t const packetlen, + VALUE_PAIR **pvp) +{ + int8_t tag = TAG_NONE; + size_t datalen; + ssize_t rcode; + uint32_t vendor; + DICT_ATTR const *child; + VALUE_PAIR *vp; + uint8_t const *data = start; + char *p; + uint8_t buffer[256]; + + /* + * FIXME: Attrlen can be larger than 253 for extended attrs! + */ + if (!da || (attrlen > packetlen) || + ((attrlen > 253) && (attrlen != packetlen)) || + (attrlen > 128*1024)) { + fr_strerror_printf("data2vp: invalid arguments"); + return -1; + } + + VP_HEXDUMP("data2vp", start, attrlen); + + VP_TRACE("parent %s len %zu ... %zu\n", da->name, attrlen, packetlen); + + datalen = attrlen; + + /* + * Hacks for CUI. The WiMAX spec says that it can be + * zero length, even though this is forbidden by the + * RADIUS specs. So... we make a special case for it. + */ + if (attrlen == 0) { + if (!((da->vendor == 0) && + (da->attr == PW_CHARGEABLE_USER_IDENTITY))) { + *pvp = NULL; + return 0; + } + + /* + * Create a zero-length attribute. + */ + vp = fr_pair_afrom_da(ctx, da); + if (!vp) return -1; + goto done; + } + + /* + * Hacks for tags. If the attribute is capable of + * encoding a tag, and there's room for the tag, and + * there is a tag, or it's encrypted with Tunnel-Password, + * then decode the tag. + */ + if (da->flags.has_tag && (datalen > 1) && + ((data[0] < 0x20) +#ifndef WITH_RADIUSV11_ONLY + || (da->flags.encrypt == FLAG_ENCRYPT_TUNNEL_PASSWORD) +#endif + )) { + + /* + * Only "short" attributes can be encrypted. + */ + if (datalen >= sizeof(buffer)) return -1; + + if (da->type == PW_TYPE_STRING) { + memcpy(buffer, data + 1, datalen - 1); + tag = data[0]; + datalen -= 1; + + } else if (da->type == PW_TYPE_INTEGER) { + memcpy(buffer, data, attrlen); + tag = buffer[0]; + buffer[0] = 0; + + } else { + return -1; /* only string and integer can have tags */ + } + + data = buffer; + } + +#ifndef WITH_RADIUSV11_ONLY + /* + * Decrypt the attribute. + */ + if (secret && packet && + +#ifdef WITH_RADIUSV11 + /* + * RADIUSV11 does not encrypt any attributes. + */ + !packet->radiusv11 && +#endif + + (da->flags.encrypt != FLAG_ENCRYPT_NONE)) { + VP_TRACE("data2vp: decrypting type %u\n", da->flags.encrypt); + /* + * Encrypted attributes can only exist for the + * old-style format. Extended attributes CANNOT + * be encrypted. + */ + if (attrlen > 253) { + return -1; + } + + if (data == start) { + memcpy(buffer, data, attrlen); + } + data = buffer; + + switch (da->flags.encrypt) { /* can't be tagged */ + /* + * User-Password + */ + case FLAG_ENCRYPT_USER_PASSWORD: + if (original) { + rad_pwdecode((char *) buffer, + attrlen, secret, + original->vector); + } else { + rad_pwdecode((char *) buffer, + attrlen, secret, + packet->vector); + } + buffer[253] = '\0'; + + /* + * MS-CHAP-MPPE-Keys are 24 octets, and + * encrypted. Since it's binary, we can't + * look for trailing zeros. + */ + if (da->flags.length) { + if (datalen > da->flags.length) { + datalen = da->flags.length; + } /* else leave datalen alone */ + } else { + /* + * Take off trailing zeros from the END. + * This allows passwords to have zeros in + * the middle of a field. + * + * However, if the password has a zero at + * the end, it will get mashed by this + * code. There's really no way around + * that. + */ + while ((datalen > 0) && (buffer[datalen - 1] == '\0')) datalen--; + } + break; + + /* + * Tunnel-Password's may go ONLY in response + * packets. They can have a tag, so datalen is + * not the same as attrlen. + */ + case FLAG_ENCRYPT_TUNNEL_PASSWORD: + if (rad_tunnel_pwdecode(buffer, &datalen, secret, + original ? original->vector : nullvector) < 0) { + goto raw; + } + break; + + /* + * Ascend-Send-Secret + * Ascend-Receive-Secret + */ + case FLAG_ENCRYPT_ASCEND_SECRET: + if (!original) { + goto raw; + } else { + uint8_t my_digest[AUTH_VECTOR_LEN]; + size_t secret_len; + + secret_len = datalen; + if (secret_len > AUTH_VECTOR_LEN) secret_len = AUTH_VECTOR_LEN; + + make_secret(my_digest, + original->vector, + secret, data, secret_len); + memcpy(buffer, my_digest, + AUTH_VECTOR_LEN ); + buffer[AUTH_VECTOR_LEN] = '\0'; + datalen = strlen((char *) buffer); + } + break; + + default: + break; + } /* switch over encryption flags */ + } +#endif /* WITH_RADIUSV11_ONLY */ + + /* + * Double-check the length after decrypting the + * attribute. + */ + VP_TRACE("data2vp: type %u\n", da->type); + switch (da->type) { + case PW_TYPE_STRING: + case PW_TYPE_OCTETS: + break; + + case PW_TYPE_ABINARY: + if (datalen > sizeof(vp->vp_filter)) goto raw; + break; + + case PW_TYPE_INTEGER: + case PW_TYPE_IPV4_ADDR: + case PW_TYPE_DATE: + case PW_TYPE_SIGNED: + if (datalen != 4) goto raw; + break; + + case PW_TYPE_INTEGER64: + case PW_TYPE_IFID: + if (datalen != 8) goto raw; + break; + + case PW_TYPE_IPV6_ADDR: + if (datalen != 16) goto raw; + break; + + case PW_TYPE_IPV6_PREFIX: + if ((datalen < 2) || (datalen > 18)) goto raw; + if (data[1] > 128) goto raw; + break; + + case PW_TYPE_BYTE: + if (datalen != 1) goto raw; + break; + + case PW_TYPE_SHORT: + if (datalen != 2) goto raw; + break; + + case PW_TYPE_ETHERNET: + if (datalen != 6) goto raw; + break; + + case PW_TYPE_COMBO_IP_ADDR: + if (datalen == 4) { + child = dict_attrbytype(da->attr, da->vendor, + PW_TYPE_IPV4_ADDR); + } else if (datalen == 16) { + child = dict_attrbytype(da->attr, da->vendor, + PW_TYPE_IPV6_ADDR); + } else { + goto raw; + } + if (!child) goto raw; + da = child; /* re-write it */ + break; + + case PW_TYPE_IPV4_PREFIX: + if (datalen != 6) goto raw; + if ((data[1] & 0x3f) > 32) goto raw; + break; + + /* + * The rest of the data types can cause + * recursion! Ask yourself, "is recursion OK?" + */ + + case PW_TYPE_EXTENDED: + if (datalen < 2) goto raw; /* etype, value */ + + child = dict_attrbyparent(da, data[0], 0); + if (!child) goto raw; + + /* + * Recurse to decode the contents, which could be + * a TLV, IPaddr, etc. Note that we decode only + * the current attribute, and we ignore any extra + * data after it. + */ + rcode = data2vp(ctx, packet, original, secret, child, + data + 1, attrlen - 1, attrlen - 1, pvp); + if (rcode < 0) goto raw; + return 1 + rcode; + + case PW_TYPE_LONG_EXTENDED: + if (datalen < 3) goto raw; /* etype, flags, value */ + + child = dict_attrbyparent(da, data[0], 0); + if (!child) { + if ((data[0] != PW_VENDOR_SPECIFIC) || + (datalen < (3 + 4 + 1))) { + /* da->attr < 255, da->vendor == 0 */ + child = dict_unknown_afrom_fields(ctx, data[0], da->attr * FR_MAX_VENDOR); + } else { + /* + * Try to find the VSA. + */ + memcpy(&vendor, data + 3, 4); + vendor = ntohl(vendor); + + if (vendor == 0) goto raw; + + child = dict_unknown_afrom_fields(ctx, data[7], vendor | (da->attr * FR_MAX_VENDOR)); + } + + if (!child) { + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } + } + + /* + * This requires a whole lot more work. + */ + return data2vp_extended(ctx, packet, original, secret, child, + start, attrlen, packetlen, pvp); + + case PW_TYPE_EVS: + if (datalen < 6) goto raw; /* vid, vtype, value */ + + if (data[0] != 0) goto raw; /* we require 24-bit VIDs */ + + memcpy(&vendor, data, 4); + vendor = ntohl(vendor); + vendor |= da->vendor; + + child = dict_attrbyvalue(data[4], vendor); + if (!child) { + /* + * Create a "raw" attribute from the + * contents of the EVS VSA. + */ + da = dict_unknown_afrom_fields(ctx, data[4], vendor); + data += 5; + datalen -= 5; + break; + } + + rcode = data2vp(ctx, packet, original, secret, child, + data + 5, attrlen - 5, attrlen - 5, pvp); + if (rcode < 0) goto raw; + return 5 + rcode; + + case PW_TYPE_TLV: + /* + * We presume that the TLVs all fit into one + * attribute, OR they've already been grouped + * into a contiguous memory buffer. + */ + rcode = rad_data2vp_tlvs(ctx, packet, original, secret, da, + data, attrlen, pvp); + if (rcode < 0) goto raw; + return rcode; + + case PW_TYPE_VSA: + /* + * VSAs can be WiMAX, in which case they don't + * fit into one attribute. + */ + rcode = data2vp_vsas(ctx, packet, original, secret, + data, attrlen, packetlen, pvp); + if (rcode < 0) goto raw; + return rcode; + + default: + raw: + /* + * If it's already unknown, don't create a new + * unknown one. + */ + if (da->flags.is_unknown) break; + + /* + * Re-write the attribute to be "raw". It is + * therefore of type "octets", and will be + * handled below. + * + * We allocate the VP *first*, and then the da + * from it, so that there are no memory leaks. + */ + vp = fr_pair_alloc(ctx); + if (!vp) return -1; + + da = dict_unknown_afrom_fields(vp, da->attr, da->vendor); + if (!da) { + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } + tag = TAG_NONE; + vp->da = da; + goto alloc_raw; + } + + /* + * And now that we've verified the basic type + * information, decode the actual data. + */ + vp = fr_pair_afrom_da(ctx, da); + if (!vp) return -1; + +alloc_raw: + vp->vp_length = datalen; + vp->tag = tag; + + switch (da->type) { + case PW_TYPE_STRING: + p = talloc_array(vp, char, vp->vp_length + 1); +#ifdef __clang_analyzer__ + if (!p) goto fail; +#endif + memcpy(p, data, vp->vp_length); + p[vp->vp_length] = '\0'; + vp->vp_strvalue = p; + break; + + case PW_TYPE_OCTETS: + fr_pair_value_memcpy(vp, data, vp->vp_length); + break; + + case PW_TYPE_ABINARY: + if (vp->vp_length > sizeof(vp->vp_filter)) { + vp->vp_length = sizeof(vp->vp_filter); + } + memcpy(vp->vp_filter, data, vp->vp_length); + break; + + case PW_TYPE_BYTE: + vp->vp_byte = data[0]; + break; + + case PW_TYPE_SHORT: + vp->vp_short = (data[0] << 8) | data[1]; + break; + + case PW_TYPE_INTEGER: + memcpy(&vp->vp_integer, data, 4); + vp->vp_integer = ntohl(vp->vp_integer); + break; + + case PW_TYPE_INTEGER64: + memcpy(&vp->vp_integer64, data, 8); + vp->vp_integer64 = ntohll(vp->vp_integer64); + break; + + case PW_TYPE_DATE: + memcpy(&vp->vp_date, data, 4); + vp->vp_date = ntohl(vp->vp_date); + break; + + case PW_TYPE_ETHERNET: + memcpy(vp->vp_ether, data, 6); + break; + + case PW_TYPE_IPV4_ADDR: + memcpy(&vp->vp_ipaddr, data, 4); + break; + + case PW_TYPE_IFID: + memcpy(vp->vp_ifid, data, 8); + break; + + case PW_TYPE_IPV6_ADDR: + memcpy(&vp->vp_ipv6addr, data, 16); + break; + + case PW_TYPE_IPV6_PREFIX: + /* + * FIXME: double-check that + * (vp->vp_octets[1] >> 3) matches vp->vp_length + 2 + */ + memcpy(vp->vp_ipv6prefix, data, vp->vp_length); + if (vp->vp_length < 18) { + memset(((uint8_t *)vp->vp_ipv6prefix) + vp->vp_length, 0, + 18 - vp->vp_length); + } + break; + + case PW_TYPE_IPV4_PREFIX: + /* FIXME: do the same double-check as for IPv6Prefix */ + memcpy(vp->vp_ipv4prefix, data, vp->vp_length); + + /* + * /32 means "keep all bits". Otherwise, mask + * them out. + */ + if ((data[1] & 0x3f) > 32) { + uint32_t addr, mask; + + memcpy(&addr, vp->vp_octets + 2, sizeof(addr)); + mask = 1; + mask <<= (32 - (data[1] & 0x3f)); + mask--; + mask = ~mask; + mask = htonl(mask); + addr &= mask; + memcpy(vp->vp_ipv4prefix + 2, &addr, sizeof(addr)); + } + break; + + case PW_TYPE_SIGNED: /* overloaded with vp_integer */ + memcpy(&vp->vp_integer, data, 4); + vp->vp_integer = ntohl(vp->vp_integer); + break; + +#ifdef __clang_analyzer__ + fail: +#endif + default: + fr_pair_list_free(&vp); + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } + +done: + vp->type = VT_DATA; + *pvp = vp; + + return attrlen; +} + + +/** Create a "normal" VALUE_PAIR from the given data + * + */ +ssize_t rad_attr2vp(TALLOC_CTX *ctx, + RADIUS_PACKET *packet, RADIUS_PACKET const *original, + char const *secret, + uint8_t const *data, size_t length, + VALUE_PAIR **pvp) +{ + ssize_t rcode; + + DICT_ATTR const *da; + + if ((length < 2) || (data[1] < 2) || (data[1] > length)) { + fr_strerror_printf("rad_attr2vp: Insufficient data"); + return -1; + } + + da = dict_attrbyvalue(data[0], 0); + if (!da) { + VP_TRACE("attr2vp: unknown attribute %u\n", data[0]); + da = dict_unknown_afrom_fields(ctx, data[0], 0); + } + if (!da) return -1; + + /* + * Pass the entire thing to the decoding function + */ + if (da->flags.concat) { + VP_TRACE("attr2vp: concat attribute\n"); + return data2vp_concat(ctx, da, data, length, pvp); + } + + if (!da->vendor && (da->attr == PW_NAS_FILTER_RULE)) { + VP_TRACE("attr2vp: NAS-Filter-Rule attribute\n"); + return data2vp_nas_filter_rule(ctx, da, data, length, pvp); + } + + /* + * Note that we pass the entire length, not just the + * length of this attribute. The Extended or WiMAX + * attributes may have the "continuation" bit set, and + * will thus be more than one attribute in length. + */ + rcode = data2vp(ctx, packet, original, secret, da, + data + 2, data[1] - 2, length - 2, pvp); + if (rcode < 0) return rcode; + + return 2 + rcode; +} + +fr_thread_local_setup(uint8_t *, rad_vp2data_buff) + +/** Converts vp_data to network byte order + * + * Provide a pointer to a buffer which contains the value of the VALUE_PAIR + * in an architecture independent format. + * + * The pointer is only guaranteed to be valid between calls to rad_vp2data, and so long + * as the source VALUE_PAIR is not freed. + * + * @param out where to write the pointer to the value. + * @param vp to get the value from. + * @return -1 on error, or the length of the value + */ +ssize_t rad_vp2data(uint8_t const **out, VALUE_PAIR const *vp) +{ + uint8_t *buffer; + uint32_t lvalue; + uint64_t lvalue64; + + *out = NULL; + + buffer = fr_thread_local_init(rad_vp2data_buff, free); + if (!buffer) { + int ret; + + buffer = malloc(sizeof(uint8_t) * sizeof(value_data_t)); + if (!buffer) { + fr_strerror_printf("Failed allocating memory for rad_vp2data buffer"); + return -1; + } + + ret = fr_thread_local_set(rad_vp2data_buff, buffer); + if (ret != 0) { + fr_strerror_printf("Failed setting up TLS for rad_vp2data buffer: %s", strerror(errno)); + free(buffer); + return -1; + } + } + + VERIFY_VP(vp); + + switch (vp->da->type) { + case PW_TYPE_STRING: + case PW_TYPE_OCTETS: + memcpy(out, &vp->data.ptr, sizeof(*out)); + break; + + /* + * All of these values are at the same location. + */ + case PW_TYPE_IFID: + case PW_TYPE_IPV4_ADDR: + case PW_TYPE_IPV6_ADDR: + case PW_TYPE_IPV6_PREFIX: + case PW_TYPE_IPV4_PREFIX: + case PW_TYPE_ABINARY: + case PW_TYPE_ETHERNET: + case PW_TYPE_COMBO_IP_ADDR: + case PW_TYPE_COMBO_IP_PREFIX: + { + void const *p = &vp->data; + memcpy(out, &p, sizeof(*out)); + break; + } + + case PW_TYPE_BOOLEAN: + buffer[0] = vp->vp_byte & 0x01; + *out = buffer; + break; + + case PW_TYPE_BYTE: + buffer[0] = vp->vp_byte & 0xff; + *out = buffer; + break; + + case PW_TYPE_SHORT: + buffer[0] = (vp->vp_short >> 8) & 0xff; + buffer[1] = vp->vp_short & 0xff; + *out = buffer; + break; + + case PW_TYPE_INTEGER: + lvalue = htonl(vp->vp_integer); + memcpy(buffer, &lvalue, sizeof(lvalue)); + *out = buffer; + break; + + case PW_TYPE_INTEGER64: + lvalue64 = htonll(vp->vp_integer64); + memcpy(buffer, &lvalue64, sizeof(lvalue64)); + *out = buffer; + break; + + case PW_TYPE_DATE: + lvalue = htonl(vp->vp_date); + memcpy(buffer, &lvalue, sizeof(lvalue)); + *out = buffer; + break; + + case PW_TYPE_SIGNED: + { + int32_t slvalue = htonl(vp->vp_signed); + memcpy(buffer, &slvalue, sizeof(slvalue)); + *out = buffer; + break; + } + + case PW_TYPE_INVALID: + case PW_TYPE_EXTENDED: + case PW_TYPE_LONG_EXTENDED: + case PW_TYPE_EVS: + case PW_TYPE_VSA: + case PW_TYPE_TLV: + case PW_TYPE_TIMEVAL: + case PW_TYPE_MAX: + fr_strerror_printf("Cannot get data for VALUE_PAIR type %i", vp->da->type); + return -1; + + /* Don't add default */ + } + + return vp->vp_length; +} + +/** Calculate/check digest, and decode radius attributes + * + * @return -1 on decoding error, 0 on success + */ +int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, + char const *secret) +{ + int packet_length; + uint32_t num_attributes; + uint8_t *ptr; + radius_packet_t *hdr; + VALUE_PAIR *head, **tail, *vp = NULL; + + /* + * Extract attribute-value pairs + */ + hdr = (radius_packet_t *)packet->data; + ptr = hdr->data; + packet_length = packet->data_len - RADIUS_HDR_LEN; + + head = NULL; + tail = &head; + num_attributes = 0; + + /* + * Loop over the attributes, decoding them into VPs. + */ + while (packet_length > 0) { + ssize_t my_len; + +#ifdef WITH_RADIUSV11 + /* + * Don't decode Message-Authenticator + */ + if (ptr[0] == PW_MESSAGE_AUTHENTICATOR) { + packet_length -= ptr[1]; + ptr += ptr[1]; + continue; + } + + /* + * Don't decode Original-Packet-Code + */ + if ((ptr[0] == PW_EXTENDED_ATTRIBUTE_1) && (ptr[1] >= 3) && (ptr[2] == 4)) { + packet_length -= ptr[1]; + ptr += ptr[1]; + continue; + } +#endif + + /* + * This may return many VPs + */ + my_len = rad_attr2vp(packet, packet, original, secret, + ptr, packet_length, &vp); + if (my_len < 0) { + fr_pair_list_free(&head); + return -1; + } + + *tail = vp; + while (vp) { + num_attributes++; + tail = &(vp->next); + vp = vp->next; + } + + /* + * VSA's may not have been counted properly in + * rad_packet_ok() above, as it is hard to count + * then without using the dictionary. We + * therefore enforce the limits here, too. + */ + if ((fr_max_attributes > 0) && + (num_attributes > fr_max_attributes)) { + char host_ipaddr[128]; + + fr_pair_list_free(&head); + fr_strerror_printf("Possible DoS attack from host %s: Too many attributes in request (received %d, max %d are allowed).", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), + num_attributes, fr_max_attributes); + return -1; + } + + ptr += my_len; + packet_length -= my_len; + } + + /* + * Merge information from the outside world into our + * random pool. + */ + fr_rand_seed(packet->data, RADIUS_HDR_LEN); + + /* + * There may be VP's already in the packet. Don't + * destroy them. Instead, add the decoded attributes to + * the tail of the list. + */ + for (tail = &packet->vps; *tail != NULL; tail = &((*tail)->next)) { + /* nothing */ + } + *tail = head; + + return 0; +} + +#ifndef WITH_RADIUSV11_ONLY +/** Encode password + * + * We assume that the passwd buffer passed is big enough. + * RFC2138 says the password is max 128 chars, so the size + * of the passwd buffer must be at least 129 characters. + * Preferably it's just MAX_STRING_LEN. + * + * int *pwlen is updated to the new length of the encrypted + * password - a multiple of 16 bytes. + */ +int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, + uint8_t const *vector) +{ + FR_MD5_CTX context, old; + uint8_t digest[AUTH_VECTOR_LEN]; + int i, n, secretlen; + int len; + + /* + * RFC maximum is 128 bytes. + * + * If length is zero, pad it out with zeros. + * + * If the length isn't aligned to 16 bytes, + * zero out the extra data. + */ + len = *pwlen; + + if (len > 128) len = 128; + + if (len == 0) { + memset(passwd, 0, AUTH_PASS_LEN); + len = AUTH_PASS_LEN; + } else if ((len % AUTH_PASS_LEN) != 0) { + memset(&passwd[len], 0, AUTH_PASS_LEN - (len % AUTH_PASS_LEN)); + len += AUTH_PASS_LEN - (len % AUTH_PASS_LEN); + } + *pwlen = len; + + /* + * Use the secret to setup the decryption digest + */ + secretlen = strlen(secret); + + fr_md5_init(&context); + fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, secretlen); + fr_md5_copy(old, context); /* save intermediate work */ + + /* + * Encrypt it in place. Don't bother checking + * len, as we've ensured above that it's OK. + */ + for (n = 0; n < len; n += AUTH_PASS_LEN) { + if (n == 0) { + fr_md5_update(&context, vector, AUTH_PASS_LEN); + fr_md5_final(digest, &context); + } else { + fr_md5_copy(context, old); + fr_md5_update(&context, + (uint8_t *) passwd + n - AUTH_PASS_LEN, + AUTH_PASS_LEN); + fr_md5_final(digest, &context); + } + + for (i = 0; i < AUTH_PASS_LEN; i++) { + passwd[i + n] ^= digest[i]; + } + } + + fr_md5_destroy(&old); + fr_md5_destroy(&context); + + return 0; +} + +/** Decode password + * + */ +int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, + uint8_t const *vector) +{ + FR_MD5_CTX context, old; + uint8_t digest[AUTH_VECTOR_LEN]; + int i; + size_t n, secretlen; + + /* + * The RFC's say that the maximum is 128. + * The buffer we're putting it into above is 254, so + * we don't need to do any length checking. + */ + if (pwlen > 128) pwlen = 128; + + /* + * Catch idiots. + */ + if (pwlen == 0) goto done; + + /* + * Use the secret to setup the decryption digest + */ + secretlen = strlen(secret); + + fr_md5_init(&context); + fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, secretlen); + fr_md5_copy(old, context); /* save intermediate work */ + + /* + * The inverse of the code above. + */ + for (n = 0; n < pwlen; n += AUTH_PASS_LEN) { + if (n == 0) { + fr_md5_update(&context, vector, AUTH_VECTOR_LEN); + fr_md5_final(digest, &context); + + fr_md5_copy(context, old); + if (pwlen > AUTH_PASS_LEN) { + fr_md5_update(&context, (uint8_t *) passwd, + AUTH_PASS_LEN); + } + } else { + fr_md5_final(digest, &context); + + fr_md5_copy(context, old); + if (pwlen > (n + AUTH_PASS_LEN)) { + fr_md5_update(&context, (uint8_t *) passwd + n, + AUTH_PASS_LEN); + } + } + + for (i = 0; i < AUTH_PASS_LEN; i++) { + passwd[i + n] ^= digest[i]; + } + } + + done: + fr_md5_destroy(&old); + fr_md5_destroy(&context); + + passwd[pwlen] = '\0'; + return strlen(passwd); +} + + +/** Encode Tunnel-Password attributes when sending them out on the wire + * + * int *pwlen is updated to the new length of the encrypted + * password - a multiple of 16 bytes. + * + * This is per RFC-2868 which adds a two char SALT to the initial intermediate + * value MD5 hash. + */ +ssize_t rad_tunnel_pwencode(char *passwd, size_t *pwlen, char const *secret, uint8_t const *vector) +{ + uint8_t buffer[AUTH_VECTOR_LEN + MAX_STRING_LEN + 3]; + unsigned char digest[AUTH_VECTOR_LEN]; + char* salt; + int i, n, secretlen; + unsigned len, n2; + + len = *pwlen; + + if (len > 127) len = 127; + + /* + * Shift the password 3 positions right to place a salt and original + * length, tag will be added automatically on packet send. + */ + for (n = len ; n >= 0 ; n--) passwd[n + 3] = passwd[n]; + salt = passwd; + passwd += 2; + + /* + * save original password length as first password character; + */ + *passwd = len; + len += 1; + + + /* + * Generate salt. The RFC's say: + * + * The high bit of salt[0] must be set, each salt in a + * packet should be unique, and they should be random + * + * So, we set the high bit, add in a counter, and then + * add in some CSPRNG data. should be OK.. + */ + salt[0] = (0x80 | ( ((salt_offset++) & 0x0f) << 3) | + (fr_rand() & 0x07)); + salt[1] = fr_rand(); + + /* + * Padd password to multiple of AUTH_PASS_LEN bytes. + */ + n = len % AUTH_PASS_LEN; + if (n) { + n = AUTH_PASS_LEN - n; + for (; n > 0; n--, len++) + passwd[len] = 0; + } + /* set new password length */ + *pwlen = len + 2; + + /* + * Use the secret to setup the decryption digest + */ + secretlen = strlen(secret); + memcpy(buffer, secret, secretlen); + + for (n2 = 0; n2 < len; n2+=AUTH_PASS_LEN) { + if (!n2) { + memcpy(buffer + secretlen, vector, AUTH_VECTOR_LEN); + memcpy(buffer + secretlen + AUTH_VECTOR_LEN, salt, 2); + fr_md5_calc(digest, buffer, secretlen + AUTH_VECTOR_LEN + 2); + } else { + memcpy(buffer + secretlen, passwd + n2 - AUTH_PASS_LEN, AUTH_PASS_LEN); + fr_md5_calc(digest, buffer, secretlen + AUTH_PASS_LEN); + } + + for (i = 0; i < AUTH_PASS_LEN; i++) { + passwd[i + n2] ^= digest[i]; + } + } + passwd[n2] = 0; + return 0; +} + +/** Decode Tunnel-Password encrypted attributes + * + * Defined in RFC-2868, this uses a two char SALT along with the + * initial intermediate value, to differentiate it from the + * above. + */ +ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, uint8_t const *vector) +{ + FR_MD5_CTX context, old; + uint8_t digest[AUTH_VECTOR_LEN]; + int secretlen; + size_t i, n, encrypted_len, reallen; + + encrypted_len = *pwlen; + + /* + * We need at least a salt. + */ + if (encrypted_len < 2) { + fr_strerror_printf("tunnel password is too short"); + return -1; + } + + /* + * There's a salt, but no password. Or, there's a salt + * and a 'data_len' octet. It's wrong, but at least we + * can figure out what it means: the password is empty. + * + * Note that this means we ignore the 'data_len' field, + * if the attribute length tells us that there's no + * more data. So the 'data_len' field may be wrong, + * but that's ok... + */ + if (encrypted_len <= 3) { + passwd[0] = 0; + *pwlen = 0; + return 0; + } + + encrypted_len -= 2; /* discount the salt */ + + /* + * Use the secret to setup the decryption digest + */ + secretlen = strlen(secret); + + fr_md5_init(&context); + fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, secretlen); + fr_md5_copy(old, context); /* save intermediate work */ + + /* + * Set up the initial key: + * + * b(1) = MD5(secret + vector + salt) + */ + fr_md5_update(&context, vector, AUTH_VECTOR_LEN); + fr_md5_update(&context, passwd, 2); + + reallen = 0; + for (n = 0; n < encrypted_len; n += AUTH_PASS_LEN) { + size_t base; + size_t block_len = AUTH_PASS_LEN; + + /* + * Ensure we don't overflow the input on MD5 + */ + if ((n + 2 + AUTH_PASS_LEN) > *pwlen) { + block_len = *pwlen - n - 2; + } + + if (n == 0) { + base = 1; + + fr_md5_final(digest, &context); + + fr_md5_copy(context, old); + + /* + * A quick check: decrypt the first octet + * of the password, which is the + * 'data_len' field. Ensure it's sane. + */ + reallen = passwd[2] ^ digest[0]; + if (reallen > encrypted_len) { + fr_strerror_printf("tunnel password is too long for the attribute"); + return -1; + } + + fr_md5_update(&context, passwd + 2, block_len); + + } else { + base = 0; + + fr_md5_final(digest, &context); + + fr_md5_copy(context, old); + fr_md5_update(&context, passwd + n + 2, block_len); + } + + for (i = base; i < block_len; i++) { + passwd[n + i - 1] = passwd[n + i + 2] ^ digest[i]; + } + } + + *pwlen = reallen; + passwd[reallen] = 0; + + fr_md5_destroy(&old); + fr_md5_destroy(&context); + + return reallen; +} + +/** Encode a CHAP password + * + * @bug FIXME: might not work with Ascend because + * we use vp->vp_length, and Ascend gear likes + * to send an extra '\0' in the string! + */ +int rad_chap_encode(RADIUS_PACKET *packet, uint8_t *output, int id, + VALUE_PAIR *password) +{ + int i; + uint8_t *ptr; + uint8_t string[MAX_STRING_LEN * 2 + 1]; + VALUE_PAIR *challenge; + + /* + * Sanity check the input parameters + */ + if ((packet == NULL) || (password == NULL)) { + return -1; + } + + /* + * Note that the password VP can be EITHER + * a User-Password attribute (from a check-item list), + * or a CHAP-Password attribute (the client asking + * the library to encode it). + */ + + i = 0; + ptr = string; + *ptr++ = id; + + i++; + memcpy(ptr, password->vp_strvalue, password->vp_length); + ptr += password->vp_length; + i += password->vp_length; + + /* + * Use Chap-Challenge pair if present, + * Request Authenticator otherwise. + */ + challenge = fr_pair_find_by_num(packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY); + if (challenge) { + memcpy(ptr, challenge->vp_strvalue, challenge->vp_length); + i += challenge->vp_length; + } else { + memcpy(ptr, packet->vector, AUTH_VECTOR_LEN); + i += AUTH_VECTOR_LEN; + } + + *output = id; + fr_md5_calc((uint8_t *)output + 1, (uint8_t *)string, i); + + return 0; +} +#endif /* WITH_RADIUSV11_ONLYx */ + + +/** Seed the random number generator + * + * May be called any number of times. + */ +void fr_rand_seed(void const *data, size_t size) +{ + uint32_t hash; + + /* + * Ensure that the pool is initialized. + */ + if (!fr_rand_initialized) { + int fd; + + memset(&fr_rand_pool, 0, sizeof(fr_rand_pool)); + + fd = open("/dev/urandom", O_RDONLY); + if (fd >= 0) { + size_t total; + ssize_t this; + + total = 0; + while (total < sizeof(fr_rand_pool.randrsl)) { + this = read(fd, fr_rand_pool.randrsl, + sizeof(fr_rand_pool.randrsl) - total); + if ((this < 0) && (errno != EINTR)) break; + if (this > 0) total += this; + } + close(fd); + } else { + fr_rand_pool.randrsl[0] = fd; + fr_rand_pool.randrsl[1] = time(NULL); + fr_rand_pool.randrsl[2] = errno; + } + + fr_randinit(&fr_rand_pool, 1); + fr_rand_pool.randcnt = 0; + fr_rand_initialized = 1; + } + + if (!data) return; + + /* + * Hash the user data + */ + hash = fr_rand(); + if (!hash) hash = fr_rand(); + hash = fr_hash_update(data, size, hash); + + fr_rand_pool.randmem[fr_rand_pool.randcnt & 0xff] ^= hash; +} + + +/** Return a 32-bit random number + * + */ +uint32_t fr_rand(void) +{ + uint32_t num; + + /* + * Ensure that the pool is initialized. + */ + if (!fr_rand_initialized) { + fr_rand_seed(NULL, 0); + } + + num = fr_rand_pool.randrsl[fr_rand_pool.randcnt++ & 0xff]; + if (fr_rand_pool.randcnt >= 256) { + fr_rand_pool.randcnt = 0; + fr_isaac(&fr_rand_pool); + } + + return num; +} + + +/** Allocate a new RADIUS_PACKET + * + * @param ctx the context in which the packet is allocated. May be NULL if + * the packet is not associated with a REQUEST. + * @param new_vector if true a new request authenticator will be generated. + * @return a new RADIUS_PACKET or NULL on error. + */ +RADIUS_PACKET *rad_alloc(TALLOC_CTX *ctx, bool new_vector) +{ + RADIUS_PACKET *rp; + + rp = talloc_zero(ctx, RADIUS_PACKET); + if (!rp) { + fr_strerror_printf("out of memory"); + return NULL; + } + rp->id = -1; + rp->offset = -1; + + if (new_vector) { + int i; + uint32_t hash, base; + + /* + * Don't expose the actual contents of the random + * pool. + */ + base = fr_rand(); + for (i = 0; i < AUTH_VECTOR_LEN; i += sizeof(uint32_t)) { + hash = fr_rand() ^ base; + memcpy(rp->vector + i, &hash, sizeof(hash)); + } + } + fr_rand(); /* stir the pool again */ + + return rp; +} + +/** Allocate a new RADIUS_PACKET response + * + * @param ctx the context in which the packet is allocated. May be NULL if + * the packet is not associated with a REQUEST. + * @param packet The request packet. + * @return a new RADIUS_PACKET or NULL on error. + */ +RADIUS_PACKET *rad_alloc_reply(TALLOC_CTX *ctx, RADIUS_PACKET *packet) +{ + RADIUS_PACKET *reply; + + if (!packet) return NULL; + + reply = rad_alloc(ctx, false); + if (!reply) return NULL; + + /* + * Initialize the fields from the request. + */ + reply->sockfd = packet->sockfd; + reply->dst_ipaddr = packet->src_ipaddr; + reply->src_ipaddr = packet->dst_ipaddr; + reply->dst_port = packet->src_port; + reply->src_port = packet->dst_port; + reply->id = packet->id; + reply->code = 0; /* UNKNOWN code */ + memcpy(reply->vector, packet->vector, + sizeof(reply->vector)); + reply->vps = NULL; + reply->data = NULL; + reply->data_len = 0; + +#ifdef WITH_TCP + reply->proto = packet->proto; +#ifdef WITH_RADIUSV11 + reply->radiusv11 = packet->radiusv11; +#endif +#endif + return reply; +} + + +/** Free a RADIUS_PACKET + * + */ +void rad_free(RADIUS_PACKET **radius_packet_ptr) +{ + RADIUS_PACKET *radius_packet; + + if (!radius_packet_ptr || !*radius_packet_ptr) return; + radius_packet = *radius_packet_ptr; + + VERIFY_PACKET(radius_packet); + + fr_pair_list_free(&radius_packet->vps); + + talloc_free(radius_packet); + *radius_packet_ptr = NULL; +} + +/** Duplicate a RADIUS_PACKET + * + * @param ctx the context in which the packet is allocated. May be NULL if + * the packet is not associated with a REQUEST. + * @param in The packet to copy + * @return a new RADIUS_PACKET or NULL on error. + */ +RADIUS_PACKET *rad_copy_packet(TALLOC_CTX *ctx, RADIUS_PACKET const *in) +{ + RADIUS_PACKET *out; + + out = rad_alloc(ctx, false); + if (!out) return NULL; + + /* + * Bootstrap by copying everything. + */ + memcpy(out, in, sizeof(*out)); + + /* + * Then reset necessary fields + */ + out->sockfd = -1; + + out->data = NULL; + out->data_len = 0; + + out->vps = fr_pair_list_copy(out, in->vps); + out->offset = 0; + + return out; +} + +#ifdef WITH_RADIUSV11 +const FR_NAME_NUMBER radiusv11_types[] = { + { "forbid", FR_RADIUSV11_FORBID }, + { "allow", FR_RADIUSV11_ALLOW }, + { "require", FR_RADIUSV11_REQUIRE }, + { NULL, 0 } + +}; +#endif diff --git a/src/lib/rbtree.c b/src/lib/rbtree.c new file mode 100644 index 0000000..d9892bd --- /dev/null +++ b/src/lib/rbtree.c @@ -0,0 +1,744 @@ +/* + * rbtree.c RED-BLACK balanced binary trees. + * + * Version: $Id$ + * + * 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.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2004,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#ifdef HAVE_PTHREAD_H +#include <pthread.h> + +#define PTHREAD_MUTEX_LOCK(_x) if (_x->lock) pthread_mutex_lock(&((_x)->mutex)) +#define PTHREAD_MUTEX_UNLOCK(_x) if (_x->lock) pthread_mutex_unlock(&((_x)->mutex)) +#else +#define PTHREAD_MUTEX_LOCK(_x) +#define PTHREAD_MUTEX_UNLOCK(_x) +#endif + +/* Red-Black tree description */ +typedef enum { + BLACK, + RED +} node_colour_t; + +struct rbnode_t { + rbnode_t *left; //!< Left child + rbnode_t *right; //!< Right child + rbnode_t *parent; //!< Parent + node_colour_t colour; //!< Node colour (BLACK, RED) + void *data; //!< data stored in node +}; + +#define NIL &sentinel /* all leafs are sentinels */ +static rbnode_t sentinel = { NIL, NIL, NIL, BLACK, NULL}; + +#define NOT_AT_ROOT(_node) ((_node) != NIL) + +struct rbtree_t { +#ifndef NDEBUG + uint32_t magic; +#endif + rbnode_t *root; + int num_elements; + rb_comparator_t compare; + rb_free_t free; + bool replace; +#ifdef HAVE_PTHREAD_H + bool lock; + pthread_mutex_t mutex; +#endif +}; +#define RBTREE_MAGIC (0x5ad09c42) + +/** Walks the tree to delete all nodes Does NOT re-balance it! + * + */ +static void free_walker(rbtree_t *tree, rbnode_t *x) +{ + (void) talloc_get_type_abort(x, rbnode_t); + + if (x->left != NIL) free_walker(tree, x->left); + if (x->right != NIL) free_walker(tree, x->right); + + if (tree->free) tree->free(x->data); + talloc_free(x); +} + +void rbtree_free(rbtree_t *tree) +{ + if (!tree) return; + + PTHREAD_MUTEX_LOCK(tree); + + /* + * walk the tree, deleting the nodes... + */ + if (tree->root != NIL) free_walker(tree, tree->root); + +#ifndef NDEBUG + tree->magic = 0; +#endif + tree->root = NULL; + + PTHREAD_MUTEX_UNLOCK(tree); + +#ifdef HAVE_PTHREAD_H + if (tree->lock) pthread_mutex_destroy(&tree->mutex); +#endif + + talloc_free(tree); +} + +/** Create a new RED-BLACK tree + * + */ +rbtree_t *rbtree_create(TALLOC_CTX *ctx, rb_comparator_t compare, rb_free_t node_free, int flags) +{ + rbtree_t *tree; + + if (!compare) return NULL; + + tree = talloc_zero(ctx, rbtree_t); + if (!tree) return NULL; + +#ifndef NDEBUG + tree->magic = RBTREE_MAGIC; +#endif + tree->root = NIL; + tree->compare = compare; + tree->replace = (flags & RBTREE_FLAG_REPLACE) != 0 ? true : false; +#ifdef HAVE_PTHREAD_H + tree->lock = (flags & RBTREE_FLAG_LOCK) != 0 ? true : false; + if (tree->lock) { + pthread_mutex_init(&tree->mutex, NULL); + } +#endif + tree->free = node_free; + + return tree; +} + +/** Rotate Node x to left + * + */ +static void rotate_left(rbtree_t *tree, rbnode_t *x) +{ + + rbnode_t *y = x->right; + + /* establish x->right link */ + x->right = y->left; + if (y->left != NIL) y->left->parent = x; + + /* establish y->parent link */ + if (y != NIL) y->parent = x->parent; + if (NOT_AT_ROOT(x->parent)) { + if (x == x->parent->left) { + x->parent->left = y; + } else { + x->parent->right = y; + } + } else { + tree->root = y; + } + + /* link x and y */ + y->left = x; + if (x != NIL) x->parent = y; +} + +/** Rotate Node x to right + * + */ +static void rotate_right(rbtree_t *tree, rbnode_t *x) +{ + rbnode_t *y = x->left; + + /* establish x->left link */ + x->left = y->right; + if (y->right != NIL) y->right->parent = x; + + /* establish y->parent link */ + if (y != NIL) y->parent = x->parent; + if (NOT_AT_ROOT(x->parent)) { + if (x == x->parent->right) { + x->parent->right = y; + } else { + x->parent->left = y; + } + } else { + tree->root = y; + } + + /* link x and y */ + y->right = x; + if (x != NIL) x->parent = y; +} + +/** Maintain red-black tree balance after inserting node x + * + */ +static void insert_fixup(rbtree_t *tree, rbnode_t *x) +{ + /* check RED-BLACK properties */ + while ((x != tree->root) && (x->parent->colour == RED)) { + /* we have a violation */ + if (x->parent == x->parent->parent->left) { + rbnode_t *y = x->parent->parent->right; + if (y->colour == RED) { + + /* uncle is RED */ + x->parent->colour = BLACK; + y->colour = BLACK; + x->parent->parent->colour = RED; + x = x->parent->parent; + } else { + + /* uncle is BLACK */ + if (x == x->parent->right) { + /* make x a left child */ + x = x->parent; + rotate_left(tree, x); + } + + /* recolour and rotate */ + x->parent->colour = BLACK; + x->parent->parent->colour = RED; + rotate_right(tree, x->parent->parent); + } + } else { + + /* mirror image of above code */ + rbnode_t *y = x->parent->parent->left; + if (y->colour == RED) { + + /* uncle is RED */ + x->parent->colour = BLACK; + y->colour = BLACK; + x->parent->parent->colour = RED; + x = x->parent->parent; + } else { + + /* uncle is BLACK */ + if (x == x->parent->left) { + x = x->parent; + rotate_right(tree, x); + } + x->parent->colour = BLACK; + x->parent->parent->colour = RED; + rotate_left(tree, x->parent->parent); + } + } + } + + if (tree->root != NIL) tree->root->colour = BLACK; /* Avoid cache-dirty on NIL */ +} + + +/** Insert an element into the tree + * + */ +rbnode_t *rbtree_insert_node(rbtree_t *tree, void *data) +{ + rbnode_t *current, *parent, *x; + + PTHREAD_MUTEX_LOCK(tree); + + /* find where node belongs */ + current = tree->root; + parent = NIL; + while (current != NIL) { + int result; + + /* + * See if two entries are identical. + */ + result = tree->compare(data, current->data); + if (result == 0) { + /* + * Don't replace the entry. + */ + if (!tree->replace) { + PTHREAD_MUTEX_UNLOCK(tree); + return NULL; + } + + /* + * Do replace the entry. + */ + if (tree->free) tree->free(current->data); + current->data = data; + PTHREAD_MUTEX_UNLOCK(tree); + return current; + } + + parent = current; + current = (result < 0) ? current->left : current->right; + } + + /* setup new node */ + x = talloc_zero(tree, rbnode_t); + if (!x) { + fr_strerror_printf("No memory for new rbtree node"); + PTHREAD_MUTEX_UNLOCK(tree); + return NULL; + } + + x->data = data; + x->parent = parent; + x->left = NIL; + x->right = NIL; + x->colour = RED; + + /* insert node in tree */ + if (NOT_AT_ROOT(parent)) { + if (tree->compare(data, parent->data) <= 0) { + parent->left = x; + } else { + parent->right = x; + } + } else { + tree->root = x; + } + + insert_fixup(tree, x); + + tree->num_elements++; + + PTHREAD_MUTEX_UNLOCK(tree); + return x; +} + +bool rbtree_insert(rbtree_t *tree, void *data) +{ + if (rbtree_insert_node(tree, data)) return true; + return false; +} + +/** Maintain RED-BLACK tree balance after deleting node x + * + */ +static void delete_fixup(rbtree_t *tree, rbnode_t *x, rbnode_t *parent) +{ + + while (x != tree->root && x->colour == BLACK) { + if (x == parent->left) { + rbnode_t *w = parent->right; + if (w->colour == RED) { + w->colour = BLACK; + parent->colour = RED; /* parent != NIL? */ + rotate_left(tree, parent); + w = parent->right; + } + if ((w->left->colour == BLACK) && (w->right->colour == BLACK)) { + if (w != NIL) w->colour = RED; + x = parent; + parent = x->parent; + } else { + if (w->right->colour == BLACK) { + if (w->left != NIL) w->left->colour = BLACK; + w->colour = RED; + rotate_right(tree, w); + w = parent->right; + } + w->colour = parent->colour; + if (parent != NIL) parent->colour = BLACK; + if (w->right->colour != BLACK) { + w->right->colour = BLACK; + } + rotate_left(tree, parent); + x = tree->root; + } + } else { + rbnode_t *w = parent->left; + if (w->colour == RED) { + w->colour = BLACK; + parent->colour = RED; /* parent != NIL? */ + rotate_right(tree, parent); + w = parent->left; + } + if ((w->right->colour == BLACK) && (w->left->colour == BLACK)) { + if (w != NIL) w->colour = RED; + x = parent; + parent = x->parent; + } else { + if (w->left->colour == BLACK) { + if (w->right != NIL) w->right->colour = BLACK; + w->colour = RED; + rotate_left(tree, w); + w = parent->left; + } + w->colour = parent->colour; + if (parent != NIL) parent->colour = BLACK; + if (w->left->colour != BLACK) { + w->left->colour = BLACK; + } + rotate_right(tree, parent); + x = tree->root; + } + } + } + if (x != NIL) x->colour = BLACK; /* Avoid cache-dirty on NIL */ +} + +/** Delete an element (z) from the tree + * + */ +static void rbtree_delete_internal(rbtree_t *tree, rbnode_t *z, bool skiplock) +{ + rbnode_t *x, *y; + rbnode_t *parent; + + if (!z || z == NIL) return; + + if (!skiplock) { + PTHREAD_MUTEX_LOCK(tree); + } + + if (z->left == NIL || z->right == NIL) { + /* y has a NIL node as a child */ + y = z; + } else { + /* find tree successor with a NIL node as a child */ + y = z->right; + while (y->left != NIL) y = y->left; + } + + /* x is y's only child */ + if (y->left != NIL) { + x = y->left; + } else { + x = y->right; /* may be NIL! */ + } + + /* remove y from the parent chain */ + parent = y->parent; + if (x != NIL) x->parent = parent; + + if (NOT_AT_ROOT(parent)) { + if (y == parent->left) { + parent->left = x; + } else { + parent->right = x; + } + } else { + tree->root = x; + } + + if (y != z) { + if (tree->free) tree->free(z->data); + z->data = y->data; + y->data = NULL; + + if ((y->colour == BLACK) && NOT_AT_ROOT(parent)) { + delete_fixup(tree, x, parent); + } + + /* + * The user structure in y->data MAy include a + * pointer to y. In that case, we CANNOT delete + * y. Instead, we copy z (which is now in the + * tree) to y, and fix up the parent/child + * pointers. + */ + memcpy(y, z, sizeof(*y)); + + if (NOT_AT_ROOT(y->parent)) { + if (y->parent->left == z) y->parent->left = y; + if (y->parent->right == z) y->parent->right = y; + } else { + tree->root = y; + } + if (y->left->parent == z) y->left->parent = y; + if (y->right->parent == z) y->right->parent = y; + + talloc_free(z); + + } else { + if (tree->free) tree->free(y->data); + + if (y->colour == BLACK) + delete_fixup(tree, x, parent); + + talloc_free(y); + } + + tree->num_elements--; + if (!skiplock) { + PTHREAD_MUTEX_UNLOCK(tree); + } +} +void rbtree_delete(rbtree_t *tree, rbnode_t *z) { + rbtree_delete_internal(tree, z, false); +} + +/** Delete a node from the tree, based on given data, which MUST have come from rbtree_finddata(). + * + * + */ +bool rbtree_deletebydata(rbtree_t *tree, void const *data) +{ + rbnode_t *node = rbtree_find(tree, data); + + if (!node) return false; + + rbtree_delete(tree, node); + + return true; +} + + +/** Find an element in the tree, returning the data, not the node + * + */ +rbnode_t *rbtree_find(rbtree_t *tree, void const *data) +{ + rbnode_t *current; + + PTHREAD_MUTEX_LOCK(tree); + current = tree->root; + + while (current != NIL) { + int result = tree->compare(data, current->data); + + if (result == 0) { + PTHREAD_MUTEX_UNLOCK(tree); + return current; + } else { + current = (result < 0) ? + current->left : current->right; + } + } + + PTHREAD_MUTEX_UNLOCK(tree); + return NULL; +} + +/** Find the user data. + * + */ +void *rbtree_finddata(rbtree_t *tree, void const *data) +{ + rbnode_t *x; + + x = rbtree_find(tree, data); + if (!x) return NULL; + + return x->data; +} + +/** Walk the tree, Pre-order + * + * We call ourselves recursively for each function, but that's OK, + * as the stack is only log(N) deep, which is ~12 entries deep. + */ +static int walk_node_pre_order(rbnode_t *x, rb_walker_t compare, void *context) +{ + int rcode; + rbnode_t *left, *right; + + left = x->left; + right = x->right; + + rcode = compare(context, x->data); + if (rcode != 0) return rcode; + + if (left != NIL) { + rcode = walk_node_pre_order(left, compare, context); + if (rcode != 0) return rcode; + } + + if (right != NIL) { + rcode = walk_node_pre_order(right, compare, context); + if (rcode != 0) return rcode; + } + + return 0; /* we know everything returned zero */ +} + +/** rbtree_in_order + * + */ +static int walk_node_in_order(rbnode_t *x, rb_walker_t compare, void *context) +{ + int rcode; + rbnode_t *right; + + if (x->left != NIL) { + rcode = walk_node_in_order(x->left, compare, context); + if (rcode != 0) return rcode; + } + + right = x->right; + + rcode = compare(context, x->data); + if (rcode != 0) return rcode; + + if (right != NIL) { + rcode = walk_node_in_order(right, compare, context); + if (rcode != 0) return rcode; + } + + return 0; /* we know everything returned zero */ +} + + +/** rbtree_post_order + * + */ +static int walk_node_post_order(rbnode_t *x, rb_walker_t compare, void *context) +{ + int rcode; + + if (x->left != NIL) { + rcode = walk_node_post_order(x->left, compare, context); + if (rcode != 0) return rcode; + } + + if (x->right != NIL) { + rcode = walk_node_post_order(x->right, compare, context); + if (rcode != 0) return rcode; + } + + rcode = compare(context, x->data); + if (rcode != 0) return rcode; + + return 0; /* we know everything returned zero */ +} + + +/** rbtree_delete_order + * + * This executes an rbtree_in_order-like walk that adapts to changes in the + * tree above it, which may occur because we allow the compare to + * tell us to delete the current node. + * + * The compare should return: + * + * < 0 - on error + * 0 - continue walking, don't delete the node + * 1 - delete the node and stop walking + * 2 - delete the node and continue walking + */ +static int walk_delete_order(rbtree_t *tree, rb_walker_t compare, void *context) +{ + rbnode_t *solid, *x; + int rcode = 0; + + /* Keep track of last node that refused deletion. */ + solid = NIL; + while (solid == NIL) { + x = tree->root; + if (x == NIL) break; + descend: + while (x->left != NIL) { + x = x->left; + } + visit: + rcode = compare(context, x->data); + if (rcode < 0) { + return rcode; + } + if (rcode) { + rbtree_delete_internal(tree, x, true); + if (rcode != 2) { + return rcode; + } + } else { + solid = x; + } + } + if (solid != NIL) { + x = solid; + if (x->right != NIL) { + x = x->right; + goto descend; + } + while (NOT_AT_ROOT(x->parent)) { + if (x->parent->left == x) { + x = x->parent; + goto visit; + } + x = x->parent; + } + } + return rcode; +} + + +/* + * walk the entire tree. The compare function CANNOT modify + * the tree. + * + * The compare function should return 0 to continue walking. + * Any other value stops the walk, and is returned. + */ +int rbtree_walk(rbtree_t *tree, rb_order_t order, rb_walker_t compare, void *context) +{ + int rcode; + + if (tree->root == NIL) return 0; + + PTHREAD_MUTEX_LOCK(tree); + + switch (order) { + case RBTREE_PRE_ORDER: + rcode = walk_node_pre_order(tree->root, compare, context); + break; + + case RBTREE_IN_ORDER: + rcode = walk_node_in_order(tree->root, compare, context); + break; + + case RBTREE_POST_ORDER: + rcode = walk_node_post_order(tree->root, compare, context); + break; + + case RBTREE_DELETE_ORDER: + rcode = walk_delete_order(tree, compare, context); + break; + + default: + rcode = -1; + break; + } + + PTHREAD_MUTEX_UNLOCK(tree); + return rcode; +} + +uint32_t rbtree_num_elements(rbtree_t *tree) +{ + if (!tree) return 0; + + return tree->num_elements; +} + +/* + * Given a Node, return the data. + */ +void *rbtree_node2data(UNUSED rbtree_t *tree, rbnode_t *node) +{ + if (!node) return NULL; + + return node->data; +} diff --git a/src/lib/regex.c b/src/lib/regex.c new file mode 100644 index 0000000..64a6dbe --- /dev/null +++ b/src/lib/regex.c @@ -0,0 +1,390 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file lib/regex.c + * @brief regex abstraction functions + * + * @copyright 2014 The FreeRADIUS server project + * @copyright 2014 Arran Cudbard-Bell <a.cudbardb@freeradius.org> + */ + +#ifdef HAVE_REGEX +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/regex.h> + +/* + * Wrapper functions for libpcre. Much more powerful, and guaranteed + * to be binary safe but require libpcre. + */ +# ifdef HAVE_PCRE +/** Free regex_t structure + * + * Calls libpcre specific free functions for the expression and study. + * + * @param preg to free. + */ +static int _regex_free(regex_t *preg) +{ + if (preg->compiled) { + pcre_free(preg->compiled); + preg->compiled = NULL; + } + + if (preg->extra) { +#ifdef PCRE_CONFIG_JIT + pcre_free_study(preg->extra); +#else + pcre_free(preg->extra); +#endif + preg->extra = NULL; + } + + return 0; +} + +/* + * Replace the libpcre malloc and free functions with + * talloc wrappers. This allows us to use the subcapture copy + * functions and just reparent the memory allocated. + */ +static void *_pcre_malloc(size_t to_alloc) +{ + return talloc_array(NULL, uint8_t, to_alloc); +} + +static void _pcre_free(void *to_free) +{ + talloc_free(to_free); +} + +/** Wrapper around pcre_compile + * + * Allows the rest of the code to do compilations using one function signature. + * + * @note Compiled expression must be freed with talloc_free. + * + * @param out Where to write out a pointer to the structure containing the compiled expression. + * @param pattern to compile. + * @param len of pattern. + * @param ignore_case whether to do case insensitive matching. + * @param multiline If true $ matches newlines. + * @param subcaptures Whether to compile the regular expression to store subcapture + * data. + * @param runtime If false run the pattern through the PCRE JIT to convert it to machine code. + * This trades startup time (longer) for runtime performance (better). + * @return >= 1 on success, <= 0 on error. Negative value is offset of parse error. + */ +ssize_t regex_compile(TALLOC_CTX *ctx, regex_t **out, char const *pattern, size_t len, + bool ignore_case, bool multiline, bool subcaptures, bool runtime) +{ + char const *error; + int offset; + int cflags = 0; + regex_t *preg; + + static bool setup = false; + + /* + * Lets us use subcapture copy + */ + if (!setup) { + pcre_malloc = _pcre_malloc; + pcre_free = _pcre_free; + setup = true; + } + + *out = NULL; + + if (len == 0) { + fr_strerror_printf("Empty expression"); + return 0; + } + + if (ignore_case) cflags |= PCRE_CASELESS; + if (multiline) cflags |= PCRE_MULTILINE; + if (!subcaptures) cflags |= PCRE_NO_AUTO_CAPTURE; + + preg = talloc_zero(ctx, regex_t); + talloc_set_destructor(preg, _regex_free); + + preg->compiled = pcre_compile(pattern, cflags, &error, &offset, NULL); + if (!preg->compiled) { + talloc_free(preg); + fr_strerror_printf("Pattern compilation failed: %s", error); + + return -(ssize_t)offset; + } + if (!runtime) { + preg->precompiled = true; + preg->extra = pcre_study(preg->compiled, PCRE_STUDY_JIT_COMPILE, &error); + if (error) { + talloc_free(preg); + fr_strerror_printf("Pattern study failed: %s", error); + + return 0; + } + } + + *out = preg; + + return len; +} + +static const FR_NAME_NUMBER regex_pcre_error_str[] = { + { "PCRE_ERROR_NOMATCH", PCRE_ERROR_NOMATCH }, + { "PCRE_ERROR_NULL", PCRE_ERROR_NULL }, + { "PCRE_ERROR_BADOPTION", PCRE_ERROR_BADOPTION }, + { "PCRE_ERROR_BADMAGIC", PCRE_ERROR_BADMAGIC }, + { "PCRE_ERROR_UNKNOWN_OPCODE", PCRE_ERROR_UNKNOWN_OPCODE }, + { "PCRE_ERROR_NOMEMORY", PCRE_ERROR_NOMEMORY }, + { "PCRE_ERROR_NOSUBSTRING", PCRE_ERROR_NOSUBSTRING }, + { "PCRE_ERROR_MATCHLIMIT", PCRE_ERROR_MATCHLIMIT }, + { "PCRE_ERROR_CALLOUT", PCRE_ERROR_CALLOUT }, + { "PCRE_ERROR_BADUTF8", PCRE_ERROR_BADUTF8 }, + { "PCRE_ERROR_BADUTF8_OFFSET", PCRE_ERROR_BADUTF8_OFFSET }, + { "PCRE_ERROR_PARTIAL", PCRE_ERROR_PARTIAL }, + { "PCRE_ERROR_BADPARTIAL", PCRE_ERROR_BADPARTIAL }, + { "PCRE_ERROR_INTERNAL", PCRE_ERROR_INTERNAL }, + { "PCRE_ERROR_BADCOUNT", PCRE_ERROR_BADCOUNT }, + { "PCRE_ERROR_DFA_UITEM", PCRE_ERROR_DFA_UITEM }, + { "PCRE_ERROR_DFA_UCOND", PCRE_ERROR_DFA_UCOND }, + { "PCRE_ERROR_DFA_UMLIMIT", PCRE_ERROR_DFA_UMLIMIT }, + { "PCRE_ERROR_DFA_WSSIZE", PCRE_ERROR_DFA_WSSIZE }, + { "PCRE_ERROR_DFA_RECURSE", PCRE_ERROR_DFA_RECURSE }, + { "PCRE_ERROR_RECURSIONLIMIT", PCRE_ERROR_RECURSIONLIMIT }, + { "PCRE_ERROR_NULLWSLIMIT", PCRE_ERROR_NULLWSLIMIT }, + { "PCRE_ERROR_BADNEWLINE", PCRE_ERROR_BADNEWLINE }, + { NULL, 0 } +}; + +/** Wrapper around pcre_exec + * + * @param preg The compiled expression. + * @param subject to match. + * @param len Length of subject. + * @param pmatch Array of match pointers. + * @param nmatch How big the match array is. Updated to number of matches. + * @return -1 on error, 0 on no match, 1 on match. + */ +int regex_exec(regex_t *preg, char const *subject, size_t len, regmatch_t pmatch[], size_t *nmatch) +{ + int ret; + size_t matches; + + /* + * PCRE_NO_AUTO_CAPTURE is a compile time only flag, + * and can't be passed here. + * We rely on the fact that matches has been set to + * 0 as a hint that no subcapture data should be + * generated. + */ + if (!pmatch || !nmatch) { + pmatch = NULL; + if (nmatch) *nmatch = 0; + matches = 0; + } else { + matches = *nmatch; + } + + ret = pcre_exec(preg->compiled, preg->extra, subject, len, 0, 0, (int *)pmatch, matches * 3); + if (ret < 0) { + if (ret == PCRE_ERROR_NOMATCH) return 0; + + fr_strerror_printf("regex evaluation failed with code (%i): %s", ret, + fr_int2str(regex_pcre_error_str, ret, "<INVALID>")); + return -1; + } + + /* + * 0 signifies more offsets than we provided space for, + * so don't touch nmatches. + */ + if (nmatch && (ret > 0)) *nmatch = ret; + + return 1; +} +/* + * Wrapper functions for POSIX like, and extended regular + * expressions. These use the system regex library. + */ +# else +/** Free heap allocated regex_t structure + * + * Heap allocation of regex_t is needed so regex_compile has the same signature with + * POSIX or libpcre. + * + * @param preg to free. + */ +static int _regex_free(regex_t *preg) +{ + regfree(preg); + + return 0; +} + +/** Binary safe wrapper around regcomp + * + * If we have the BSD extensions we don't need to do any special work + * if we don't have the BSD extensions we need to check to see if the + * regular expression contains any \0 bytes. + * + * If it does we fail and print the appropriate error message. + * + * @note Compiled expression must be freed with talloc_free. + * + * @param ctx To allocate memory in. + * @param out Where to write out a pointer to the structure containing the + * compiled expression. + * @param pattern to compile. + * @param len of pattern. + * @param ignore_case Whether the match should be case ignore_case. + * @param multiline If true $ matches newlines. + * @param subcaptures Whether to compile the regular expression to store subcapture + * data. + * @param runtime Whether the compilation is being done at runtime. + * @return >= 1 on success, <= 0 on error. Negative value is offset of parse error. + * With POSIX regex we only give the correct offset for embedded \0 errors. + */ +ssize_t regex_compile(TALLOC_CTX *ctx, regex_t **out, char const *pattern, size_t len, + bool ignore_case, bool multiline, bool subcaptures, UNUSED bool runtime) +{ + int ret; + int cflags = REG_EXTENDED; + regex_t *preg; + + if (len == 0) { + fr_strerror_printf("Empty expression"); + return 0; + } + + if (ignore_case) cflags |= REG_ICASE; + if (multiline) cflags |= REG_NEWLINE; + if (!subcaptures) cflags |= REG_NOSUB; + +#ifndef HAVE_REGNCOMP + { + char const *p; + + p = pattern; + p += strlen(pattern); + + if ((size_t)(p - pattern) != len) { + fr_strerror_printf("Found null in pattern at offset %zu. Pattern unsafe for compilation", + (p - pattern)); + return -(p - pattern); + } + + preg = talloc_zero(ctx, regex_t); + if (!preg) return 0; + + ret = regcomp(preg, pattern, cflags); + } +#else + preg = talloc_zero(ctx, regex_t); + if (!preg) return 0; + ret = regncomp(preg, pattern, len, cflags); +#endif + if (ret != 0) { + char errbuf[128]; + + regerror(ret, preg, errbuf, sizeof(errbuf)); + fr_strerror_printf("Pattern compilation failed: %s", errbuf); + + talloc_free(preg); + + return 0; /* POSIX expressions don't give us the failure offset */ + } + + talloc_set_destructor(preg, _regex_free); + *out = preg; + + return len; +} + +/** Binary safe wrapper around regexec + * + * If we have the BSD extensions we don't need to do any special work + * If we don't have the BSD extensions we need to check to see if the + * value to be compared contains any \0 bytes. + * + * If it does, we fail and print the appropriate error message. + * + * @param preg The compiled expression. + * @param subject to match. + * @param pmatch Array of match pointers. + * @param nmatch How big the match array is. Updated to number of matches. + * @return -1 on error, 0 on no match, 1 on match. + */ +int regex_exec(regex_t *preg, char const *subject, size_t len, regmatch_t pmatch[], size_t *nmatch) +{ + int ret; + size_t matches; + + /* + * Disable capturing + */ + if (!pmatch || !nmatch) { + pmatch = NULL; + if (nmatch) *nmatch = 0; + matches = 0; + } else { + /* regexec does not seem to initialise unused elements */ + matches = *nmatch; + memset(pmatch, 0, sizeof(pmatch[0]) * matches); + } + +#ifndef HAVE_REGNEXEC + { + char const *p; + + p = subject; + p += strlen(subject); + + if ((size_t)(p - subject) != len) { + fr_strerror_printf("Found null in subject at offset %zu. String unsafe for evaluation", + (p - subject)); + return -1; + } + ret = regexec(preg, subject, matches, pmatch, 0); + } +#else + ret = regnexec(preg, subject, len, matches, pmatch, 0); +#endif + if (ret != 0) { + if (ret != REG_NOMATCH) { + char errbuf[128]; + + regerror(ret, preg, errbuf, sizeof(errbuf)); + + fr_strerror_printf("regex evaluation failed: %s", errbuf); + if (nmatch) *nmatch = 0; + return -1; + } + return 0; + } + + /* + * Update *nmatch to be the maximum number of + * groups that *could* have been populated, + * need to check them later. + */ + if (nmatch && (*nmatch > preg->re_nsub)) *nmatch = preg->re_nsub + 1; + + return 1; +} +# endif +#endif diff --git a/src/lib/sha1.c b/src/lib/sha1.c new file mode 100644 index 0000000..59545bb --- /dev/null +++ b/src/lib/sha1.c @@ -0,0 +1,185 @@ +/* + * SHA-1 in C + * By Steve Reid <steve@edmweb.com> + * 100% Public Domain + * + * Version: $Id$ + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#include "../include/sha1.h" + +#ifndef WITH_OPENSSL_SHA1 +# define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ + +# define blk0(i) (block->l[i] = htonl(block->l[i])) + +# define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +# define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +# define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +# define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +# define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +# define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void fr_sha1_transform(uint32_t state[5], uint8_t const buffer[64]) +{ + uint32_t a, b, c, d, e; + typedef union { + uint8_t c[64]; + uint32_t l[16]; + } CHAR64LONG16; + CHAR64LONG16 *block; + uint8_t workspace[64]; + + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + +# ifndef __clang_analyzer__ + /* Wipe variables */ + a = b = c = d = e = 0; +# endif +} + + +/* fr_sha1_init - Initialize new context */ + +void fr_sha1_init(fr_sha1_ctx* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +/* Run your data through this. */ +void fr_sha1_update(fr_sha1_ctx *context,uint8_t const *data, size_t len) +{ + unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) { + context->count[1]++; + } + + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + fr_sha1_transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + fr_sha1_transform(context->state, &data[i]); + } + j = 0; + } else { + i = 0; + } + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void fr_sha1_final(uint8_t digest[20], fr_sha1_ctx *context) +{ + uint32_t i, j; + uint8_t finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (uint8_t)((context->count[(i >= 4 ? 0 : 1)] >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + + fr_sha1_update(context, (unsigned char const *) "\200", 1); + + while ((context->count[0] & 504) != 448) { + fr_sha1_update(context, (unsigned char const *) "\0", 1); + } + fr_sha1_update(context, finalcount, 8); /* Should cause a fr_sha1_transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (uint8_t)((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + +# ifndef __clang_analyzer__ + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +# endif + +# ifdef SHA1HANDSOFF /* make fr_sha1_transform overwrite it's own static vars */ + fr_sha1_transform(context->state, context->buffer); +# endif +} + +void fr_sha1_final_no_len(uint8_t digest[20], fr_sha1_ctx *context) +{ + uint32_t i, j; + + for (i = 0; i < 20; i++) { + digest[i] = (uint8_t)((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + +# ifndef __clang_analyzer__ + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); +# endif + +# ifdef SHA1HANDSOFF /* make fr_sha1_transform overwrite it's own static vars */ + fr_sha1_transform(context->state, context->buffer); +# endif +} +#endif diff --git a/src/lib/snprintf.c b/src/lib/snprintf.c new file mode 100644 index 0000000..9002464 --- /dev/null +++ b/src/lib/snprintf.c @@ -0,0 +1,880 @@ + +/* + Unix snprintf implementation. + Version 1.4 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Library 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 Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + Revision History: + + 1.4: + * integrate in FreeRADIUS's libradius: + * Fetched from: http://savannah.gnu.org/cgi-bin/viewcvs/mailutils/mailutils/lib/snprintf.c?rev=1.4 + * Fetched from: http://savannah.gnu.org/cgi-bin/viewcvs/mailutils/mailutils/lib/snprintf.h?rev=1.4 + * Replace config.h with autoconf.h + * Protect with HAVE_SNPRINTF and HAVE_VSNPRINTF + 1.3: + * add #include <config.h> ifdef HAVE_CONFIG_H + * cosmetic change, when exponent is 0 print xxxE+00 + instead of xxxE-00 + 1.2: + * put the program under LGPL. + 1.1: + * added changes from Miles Bader + * corrected a bug with %f + * added support for %#g + * added more comments :-) + 1.0: + * supporting must ANSI syntaxic_sugars + 0.0: + * suppot %s %c %d + + THANKS(for the patches and ideas): + Miles Bader + Cyrille Rustom + Jacek Slabocewiz + Mike Parker(mouse) + +*/ + +RCSID("$Id$") + +#include "snprintf.h" + +#ifndef HAVE_VSNPRINTF + +/* + * Find the nth power of 10 + */ +PRIVATE double +#ifdef __STDC__ +pow_10(int n) +#else +pow_10(n) +int n; +#endif +{ + int i; + double P; + + if (n < 0) + for (i = 1, P = 1., n = -n ; i <= n ; i++) {P *= .1;} + else + for (i = 1, P = 1. ; i <= n ; i++) {P *= 10.0;} + return P; +} + +/* + * Find the integral part of the log in base 10 + * Note: this not a real log10() + I just need and approximation(integerpart) of x in: + 10^x ~= r + * log_10(200) = 2; + * log_10(250) = 2; + */ +PRIVATE int +#ifdef __STDC__ +log_10(double r) +#else +log_10(r) +double r; +#endif +{ + int i = 0; + double result = 1.; + + if (r < 0.) + r = -r; + + if (r < 1.) { + while (result >= r) {result *= .1; i++;} + return (-i); + } else { + while (result <= r) {result *= 10.; i++;} + return (i - 1); + } +} + +/* + * This function return the fraction part of a double + * and set in ip the integral part. + * In many ways it resemble the modf() found on most Un*x + */ +PRIVATE double +#ifdef __STDC__ +integral(double real, double * ip) +#else +integral(real, ip) +double real; +double * ip; +#endif +{ + int j; + double i, s, p; + double real_integral = 0.; + +/* take care of the obvious */ +/* equal to zero ? */ + if (real == 0.) { + *ip = 0.; + return (0.); + } + +/* negative number ? */ + if (real < 0.) + real = -real; + +/* a fraction ? */ + if ( real < 1.) { + *ip = 0.; + return real; + } +/* the real work :-) */ + for (j = log_10(real); j >= 0; j--) { + p = pow_10(j); + s = (real - real_integral)/p; + i = 0.; + while (i + 1. <= s) {i++;} + real_integral += i*p; + } + *ip = real_integral; + return (real - real_integral); +} + +#define PRECISION 1.e-6 +/* + * return an ascii representation of the integral part of the number + * and set fract to be an ascii representation of the fraction part + * the container for the fraction and the integral part or staticly + * declare with fix size + */ +PRIVATE char * +#ifdef __STDC__ +numtoa(double number, int base, int precision, char ** fract) +#else +numtoa(number, base, precision, fract) +double number; +int base; +int precision; +char ** fract; +#endif +{ + register int i, j; + double ip, fp; /* integer and fraction part */ + double fraction; + int digits = MAX_INT - 1; + static char integral_part[MAX_INT]; + static char fraction_part[MAX_FRACT]; + double sign; + int ch; + +/* taking care of the obvious case: 0.0 */ + if (number == 0.) { + integral_part[0] = '0'; + integral_part[1] = '\0'; + fraction_part[0] = '0'; + fraction_part[1] = '\0'; + return integral_part; + } + +/* for negative numbers */ + if ((sign = number) < 0.) { + number = -number; + digits--; /* sign consume one digit */ + } + + fraction = integral(number, &ip); + number = ip; +/* do the integral part */ + if ( ip == 0.) { + integral_part[0] = '0'; + i = 1; + } else { + for ( i = 0; i < digits && number != 0.; ++i) { + number /= base; + fp = integral(number, &ip); + ch = (int)((fp + PRECISION)*base); /* force to round */ + integral_part[i] = (ch <= 9) ? ch + '0' : ch + 'a' - 10; + if (! isxdigit(integral_part[i])) /* bail out overflow !! */ + break; + number = ip; + } + } + +/* Oh No !! out of bound, ho well fill it up ! */ + if (number != 0.) + for (i = 0; i < digits; ++i) + integral_part[i] = '9'; + +/* put the sign ? */ + if (sign < 0.) + integral_part[i++] = '-'; + + integral_part[i] = '\0'; + +/* reverse every thing */ + for ( i--, j = 0; j < i; j++, i--) + SWAP_INT(integral_part[i], integral_part[j]); + +/* the fractionnal part */ + for (i=0, fp=fraction; precision > 0 && i < MAX_FRACT ; i++, precision-- ) { + fraction_part[i] = (int)((fp + PRECISION)*10. + '0'); + if (! isdigit(fraction_part[i])) /* underflow ? */ + break; + fp = (fp*10.0) - (double)(long)((fp + PRECISION)*10.); + } + fraction_part[i] = '\0'; + + if (fract != (char **)0) + *fract = fraction_part; + + return integral_part; + +} + +/* for %d and friends, it puts in holder + * the representation with the right padding + */ +PRIVATE void +#ifdef __STDC__ +decimal(struct DATA *p, double d) +#else +decimal(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp; + + tmp = itoa(d); + p->width -= strlen(tmp); + PAD_RIGHT(p); + PUT_PLUS(d, p); + PUT_SPACE(d, p); + while (*tmp) { /* the integral */ + PUT_CHAR(*tmp, p); + tmp++; + } + PAD_LEFT(p); +} + +/* for %o octal representation */ +PRIVATE void +#ifdef __STDC__ +octal(struct DATA *p, double d) +#else +octal(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp; + + tmp = otoa(d); + p->width -= strlen(tmp); + PAD_RIGHT(p); + if (p->square == FOUND) /* had prefix '0' for octal */ + PUT_CHAR('0', p); + while (*tmp) { /* octal */ + PUT_CHAR(*tmp, p); + tmp++; + } + PAD_LEFT(p); +} + +/* for %x %X hexadecimal representation */ +PRIVATE void +#ifdef __STDC__ +hexa(struct DATA *p, double d) +#else +hexa(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp; + + tmp = htoa(d); + p->width -= strlen(tmp); + PAD_RIGHT(p); + if (p->square == FOUND) { /* prefix '0x' for hexa */ + PUT_CHAR('0', p); PUT_CHAR(*p->pf, p); + } + while (*tmp) { /* hexa */ + PUT_CHAR((*p->pf == 'X' ? toupper((uint8_t) *tmp) : *tmp), p); + tmp++; + } + PAD_LEFT(p); +} + +/* %s strings */ +PRIVATE void +#ifdef __STDC__ +strings(struct DATA *p, char *tmp) +#else +strings(p, tmp) +struct DATA *p; +char *tmp; +#endif +{ + int i; + + i = strlen(tmp); + if (p->precision != NOT_FOUND) /* the smallest number */ + i = (i < p->precision ? i : p->precision); + p->width -= i; + PAD_RIGHT(p); + while (i-- > 0) { /* put the sting */ + PUT_CHAR(*tmp, p); + tmp++; + } + PAD_LEFT(p); +} + +/* %f or %g floating point representation */ +PRIVATE void +#ifdef __STDC__ +floating(struct DATA *p, double d) +#else +floating(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp, *tmp2; + int i; + + DEF_PREC(p); + d = ROUND(d, p); + tmp = dtoa(d, p->precision, &tmp2); + /* calculate the padding. 1 for the dot */ + p->width = p->width - + ((d > 0. && p->justify == RIGHT) ? 1:0) - + ((p->space == FOUND) ? 1:0) - + strlen(tmp) - p->precision - 1; + PAD_RIGHT(p); + PUT_PLUS(d, p); + PUT_SPACE(d, p); + while (*tmp) { /* the integral */ + PUT_CHAR(*tmp, p); + tmp++; + } + if (p->precision != 0 || p->square == FOUND) + PUT_CHAR('.', p); /* put the '.' */ + if (*p->pf == 'g' || *p->pf == 'G') /* smash the trailing zeros */ + for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--) + tmp2[i] = '\0'; + for (; *tmp2; tmp2++) + PUT_CHAR(*tmp2, p); /* the fraction */ + + PAD_LEFT(p); +} + +/* %e %E %g exponent representation */ +PRIVATE void +#ifdef __STDC__ +exponent(struct DATA *p, double d) +#else +exponent(p, d) +struct DATA *p; +double d; +#endif +{ + char *tmp, *tmp2; + int j, i; + + DEF_PREC(p); + j = log_10(d); + d = d / pow_10(j); /* get the Mantissa */ + d = ROUND(d, p); + tmp = dtoa(d, p->precision, &tmp2); + /* 1 for unit, 1 for the '.', 1 for 'e|E', + * 1 for '+|-', 3 for 'exp' */ + /* calculate how much padding need */ + p->width = p->width - + ((d > 0. && p->justify == RIGHT) ? 1:0) - + ((p->space == FOUND) ? 1:0) - p->precision - 7; + PAD_RIGHT(p); + PUT_PLUS(d, p); + PUT_SPACE(d, p); + while (*tmp) {/* the integral */ + PUT_CHAR(*tmp, p); + tmp++; + } + if (p->precision != 0 || p->square == FOUND) + PUT_CHAR('.', p); /* the '.' */ + if (*p->pf == 'g' || *p->pf == 'G') /* smash the trailing zeros */ + for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--) + tmp2[i] = '\0'; + for (; *tmp2; tmp2++) + PUT_CHAR(*tmp2, p); /* the fraction */ + + if (*p->pf == 'g' || *p->pf == 'e') { /* the exponent put the 'e|E' */ + PUT_CHAR('e', p); + } else + PUT_CHAR('E', p); + if (j >= 0) { /* the sign of the exp */ + PUT_CHAR('+', p); + } else { + PUT_CHAR('-', p); + j = -j; + } + tmp = itoa((double)j); + if (j < 9) { /* need to pad the exponent with 0 '000' */ + PUT_CHAR('0', p); PUT_CHAR('0', p); + } else if (j < 99) + PUT_CHAR('0', p); + while (*tmp) { /* the exponent */ + PUT_CHAR(*tmp, p); + tmp++; + } + PAD_LEFT(p); +} + +/* initialize the conversion specifiers */ +PRIVATE void +#ifdef __STDC__ +conv_flag(char * s, struct DATA * p) +#else +conv_flag(s, p) +char * s; +struct DATA * p; +#endif +{ + char number[MAX_FIELD/2]; + int i; + + /* reset the flags. */ + p->precision = p->width = NOT_FOUND; + p->star_w = p->star_p = NOT_FOUND; + p->square = p->space = NOT_FOUND; + p->a_long = p->justify = NOT_FOUND; + p->a_longlong = NOT_FOUND; + p->pad = ' '; + + for(;s && *s ;s++) { + switch (*s) { + case ' ': p->space = FOUND; break; + case '#': p->square = FOUND; break; + case '*': if (p->width == NOT_FOUND) + p->width = p->star_w = FOUND; + else + p->precision = p->star_p = FOUND; + break; + case '+': p->justify = RIGHT; break; + case '-': p->justify = LEFT; break; + case '.': if (p->width == NOT_FOUND) + p->width = 0; + break; + case '0': p->pad = '0'; break; + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': /* gob all the digits */ + for (i = 0; isdigit((uint8_t) *s); i++, s++) + if (i < MAX_FIELD/2 - 1) + number[i] = *s; + number[i] = '\0'; + if (p->width == NOT_FOUND) + p->width = atoi(number); + else + p->precision = atoi(number); + s--; /* went to far go back */ + break; + } + } +} + +PUBLIC int +#ifdef __STDC__ +vsnprintf(char *string, size_t length, char const * format, va_list args) +#else +vsnprintf(string, length, format, args) +char *string; +size_t length; +char * format; +va_list args; +#endif +{ + struct DATA data; + char conv_field[MAX_FIELD]; + double d; /* temporary holder */ + int state; + int i; + + data.length = length - 1; /* leave room for '\0' */ + data.holder = string; + data.pf = format; + data.counter = 0; + + +/* sanity check, the string must be > 1 */ + if (length < 1) + return -1; + + + for (; *data.pf && (data.counter < data.length); data.pf++) { + if ( *data.pf == '%' ) { /* we got a magic % cookie */ + conv_flag((char *)0, &data); /* initialise format flags */ + for (state = 1; *data.pf && state;) { + switch (*(++data.pf)) { + case '\0': /* a NULL here ? ? bail out */ + *data.holder = '\0'; + return data.counter; + break; + case 'f': /* float, double */ + STAR_ARGS(&data); + if (data.a_long == FOUND) + d = va_arg(args, LONG_DOUBLE); + else + d = va_arg(args, double); + floating(&data, d); + state = 0; + break; + case 'g': + case 'G': + STAR_ARGS(&data); + DEF_PREC(&data); + if (data.a_long == FOUND) + d = va_arg(args, LONG_DOUBLE); + else + d = va_arg(args, double); + i = log_10(d); + /* + * for '%g|%G' ANSI: use f if exponent + * is in the range or [-4,p] exclusively + * else use %e|%E + */ + if (-4 < i && i < data.precision) + floating(&data, d); + else + exponent(&data, d); + state = 0; + break; + case 'e': + case 'E': /* Exponent double */ + STAR_ARGS(&data); + if (data.a_long == FOUND) + d = va_arg(args, LONG_DOUBLE); + else + d = va_arg(args, double); + exponent(&data, d); + state = 0; + break; + case 'u': /* unsigned decimal */ + STAR_ARGS(&data); + if (data.a_longlong == FOUND) + d = va_arg(args, unsigned LONG_LONG); + else if (data.a_long == FOUND) + d = va_arg(args, unsigned long); + else + d = va_arg(args, unsigned int); + decimal(&data, d); + state = 0; + break; + case 'd': /* decimal */ + STAR_ARGS(&data); + if (data.a_longlong == FOUND) + d = va_arg(args, LONG_LONG); + else if (data.a_long == FOUND) + d = va_arg(args, long); + else + d = va_arg(args, int); + decimal(&data, d); + state = 0; + break; + case 'o': /* octal */ + STAR_ARGS(&data); + if (data.a_longlong == FOUND) + d = va_arg(args, LONG_LONG); + else if (data.a_long == FOUND) + d = va_arg(args, long); + else + d = va_arg(args, int); + octal(&data, d); + state = 0; + break; + case 'x': + case 'X': /* hexadecimal */ + STAR_ARGS(&data); + if (data.a_longlong == FOUND) + d = va_arg(args, LONG_LONG); + else if (data.a_long == FOUND) + d = va_arg(args, long); + else + d = va_arg(args, int); + hexa(&data, d); + state = 0; + break; + case 'c': /* character */ + d = va_arg(args, int); + PUT_CHAR(d, &data); + state = 0; + break; + case 's': /* string */ + STAR_ARGS(&data); + strings(&data, va_arg(args, char *)); + state = 0; + break; + case 'n': + *(va_arg(args, int *)) = data.counter; /* what's the count ? */ + state = 0; + break; + case 'q': + data.a_longlong = FOUND; + break; + case 'L': + case 'l': + if (data.a_long == FOUND) + data.a_longlong = FOUND; + else + data.a_long = FOUND; + break; + case 'h': + break; + case '%': /* nothing just % */ + PUT_CHAR('%', &data); + state = 0; + break; + case '#': case ' ': case '+': case '*': + case '-': case '.': case '0': case '1': + case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + /* initialize width and precision */ + for (i = 0; isflag(*data.pf); i++, data.pf++) + if (i < MAX_FIELD - 1) + conv_field[i] = *data.pf; + conv_field[i] = '\0'; + conv_flag(conv_field, &data); + data.pf--; /* went to far go back */ + break; + default: + /* is this an error ? maybe bail out */ + state = 0; + break; + } /* end switch */ + } /* end of for state */ + } else { /* not % */ + PUT_CHAR(*data.pf, &data); /* add the char the string */ + } + } + + *data.holder = '\0'; /* the end ye ! */ + + return data.counter; +} + +#endif /* HAVE_VSNPRINTF */ + +#ifndef HAVE_SNPRINTF + +PUBLIC int +#if __STDC__ +snprintf(char *string, size_t length, char const * format, ...) +#else +snprintf(string, length, format, va_alist) +char *string; +size_t length; +char * format; +va_dcl +#endif +{ + int rval; + va_list args; + +#if __STDC__ + va_start(args, format); +#else + va_start(args); +#endif + + rval = vsnprintf (string, length, format, args); + + va_end(args); + + return rval; +} + +#endif /* HAVE_SNPRINTF */ + + +#ifdef DRIVER + +#include <stdio.h> + +/* set of small tests for snprintf() */ +int main() +{ + char holder[100]; + int i; + +/* + printf("Suite of test for snprintf:\n"); + printf("a_format\n"); + printf("printf() format\n"); + printf("snprintf() format\n\n"); +*/ +/* Checking the field widths */ + + printf("/%%d/, 336\n"); + snprintf(holder, sizeof holder, "/%d/\n", 336); + printf("/%d/\n", 336); + printf("%s\n", holder); + + printf("/%%2d/, 336\n"); + snprintf(holder, sizeof holder, "/%2d/\n", 336); + printf("/%2d/\n", 336); + printf("%s\n", holder); + + printf("/%%10d/, 336\n"); + snprintf(holder, sizeof holder, "/%10d/\n", 336); + printf("/%10d/\n", 336); + printf("%s\n", holder); + + printf("/%%-10d/, 336\n"); + snprintf(holder, sizeof holder, "/%-10d/\n", 336); + printf("/%-10d/\n", 336); + printf("%s\n", holder); + +/* long long */ + + printf("/%%lld/, 336\n"); + snprintf(holder, sizeof holder, "/%lld/\n", (LONG_LONG)336); + printf("/%lld/\n", (LONG_LONG)336); + printf("%s\n", holder); + + printf("/%%2qd/, 336\n"); + snprintf(holder, sizeof holder, "/%2qd/\n", (LONG_LONG)336); + printf("/%2qd/\n", (LONG_LONG)336); + printf("%s\n", holder); + +/* floating points */ + + printf("/%%f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%f/\n", 1234.56); + printf("/%f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%e/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%e/\n", 1234.56); + printf("/%e/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%4.2f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%4.2f/\n", 1234.56); + printf("/%4.2f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%3.1f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%3.1f/\n", 1234.56); + printf("/%3.1f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%10.3f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%10.3f/\n", 1234.56); + printf("/%10.3f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%10.3e/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%10.3e/\n", 1234.56); + printf("/%10.3e/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%+4.2f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%+4.2f/\n", 1234.56); + printf("/%+4.2f/\n", 1234.56); + printf("%s\n", holder); + + printf("/%%010.2f/, 1234.56\n"); + snprintf(holder, sizeof holder, "/%010.2f/\n", 1234.56); + printf("/%010.2f/\n", 1234.56); + printf("%s\n", holder); + +#define BLURB "Outstanding acting !" +/* strings precisions */ + + printf("/%%2s/, \"%s\"\n", BLURB); + snprintf(holder, sizeof holder, "/%2s/\n", BLURB); + printf("/%2s/\n", BLURB); + printf("%s\n", holder); + + printf("/%%22s/ %s\n", BLURB); + snprintf(holder, sizeof holder, "/%22s/\n", BLURB); + printf("/%22s/\n", BLURB); + printf("%s\n", holder); + + printf("/%%22.5s/ %s\n", BLURB); + snprintf(holder, sizeof holder, "/%22.5s/\n", BLURB); + printf("/%22.5s/\n", BLURB); + printf("%s\n", holder); + + printf("/%%-22.5s/ %s\n", BLURB); + snprintf(holder, sizeof holder, "/%-22.5s/\n", BLURB); + printf("/%-22.5s/\n", BLURB); + printf("%s\n", holder); + +/* see some flags */ + + printf("%%x %%X %%#x, 31, 31, 31\n"); + snprintf(holder, sizeof holder, "%x %X %#x\n", 31, 31, 31); + printf("%x %X %#x\n", 31, 31, 31); + printf("%s\n", holder); + + printf("**%%d**%% d**%% d**, 42, 42, -42\n"); + snprintf(holder, sizeof holder, "**%d**% d**% d**\n", 42, 42, -42); + printf("**%d**% d**% d**\n", 42, 42, -42); + printf("%s\n", holder); + +/* other flags */ + + printf("/%%g/, 31.4\n"); + snprintf(holder, sizeof holder, "/%g/\n", 31.4); + printf("/%g/\n", 31.4); + printf("%s\n", holder); + + printf("/%%.6g/, 31.4\n"); + snprintf(holder, sizeof holder, "/%.6g/\n", 31.4); + printf("/%.6g/\n", 31.4); + printf("%s\n", holder); + + printf("/%%.1G/, 31.4\n"); + snprintf(holder, sizeof holder, "/%.1G/\n", 31.4); + printf("/%.1G/\n", 31.4); + printf("%s\n", holder); + + printf("abc%%n\n"); + printf("abc%n", &i); printf("%d\n", i); + snprintf(holder, sizeof holder, "abc%n", &i); + printf("%s", holder); printf("%d\n\n", i); + + printf("%%*.*s --> 10.10\n"); + snprintf(holder, sizeof holder, "%*.*s\n", 10, 10, BLURB); + printf("%*.*s\n", 10, 10, BLURB); + printf("%s\n", holder); + + printf("%%%%%%%%\n"); + snprintf(holder, sizeof holder, "%%%%\n"); + printf("%%%%\n"); + printf("%s\n", holder); + +#define BIG "Hello this is a too big string for the buffer" +/* printf("A buffer to small of 10, trying to put this:\n");*/ + printf("<%%>, %s\n", BIG); + i = snprintf(holder, 10, "%s\n", BIG); + printf("<%s>\n", BIG); + printf("<%s>\n", holder); + + return 0; +} +#endif /* !DRIVER */ diff --git a/src/lib/snprintf.h b/src/lib/snprintf.h new file mode 100644 index 0000000..c80de2d --- /dev/null +++ b/src/lib/snprintf.h @@ -0,0 +1,220 @@ +/* + Unix snprintf implementation. + Version 1.4 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Library 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 Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + Revision History: + see header of snprintf.c. + +format: + int snprintf(holder, sizeof_holder, format, ...) + +Return values: + (sizeof_holder - 1) + + + THANKS(for the patches and ideas): + Miles Bader + Cyrille Rustom + Jacek Slabocewiz + Mike Parker(mouse) + +Alain Magloire: alainm@rcsm.ee.mcgill.ca +*/ + +#ifndef HAVE_VSNPRINTF + +RCSIDH(snprintf_h, "$Id$") + +#if __STDC__ +#include <stdarg.h> +#else +#include <varargs.h> +#endif + +#include <stdlib.h> /* for atoi() */ +#include <ctype.h> + +#define PRIVATE static +#define PUBLIC + +/* + * For the FLOATING POINT FORMAT : + * the challenge was finding a way to + * manipulate the Real numbers without having + * to resort to mathematical function(it + * would require to link with -lm) and not + * going down to the bit pattern(not portable) + * + * so a number, a real is: + + real = integral + fraction + + integral = ... + a(2)*10^2 + a(1)*10^1 + a(0)*10^0 + fraction = b(1)*10^-1 + b(2)*10^-2 + ... + + where: + 0 <= a(i) => 9 + 0 <= b(i) => 9 + + from then it was simple math + */ + +/* + * size of the buffer for the integral part + * and the fraction part + */ +#define MAX_INT 99 + 1 /* 1 for the null */ +#define MAX_FRACT 29 + 1 + +/* + * If the compiler supports (long long) + */ +#ifndef LONG_LONG +# define LONG_LONG long long +/*# define LONG_LONG int64_t*/ +#endif + +/* + * If the compiler supports (long double) + */ +#ifndef LONG_DOUBLE +# define LONG_DOUBLE long double +/*# define LONG_DOUBLE double*/ +#endif + +/* + * numtoa() uses PRIVATE buffers to store the results, + * So this function is not reentrant + */ +#define itoa(n) numtoa(n, 10, 0, (char **)0) +#define otoa(n) numtoa(n, 8, 0, (char **)0) +#define htoa(n) numtoa(n, 16, 0, (char **)0) +#define dtoa(n, p, f) numtoa(n, 10, p, f) + +#define SWAP_INT(a,b) {int t; t = (a); (a) = (b); (b) = t;} + +/* this struct holds everything we need */ +struct DATA { + int length; + char *holder; + int counter; +#ifdef __STDC__ + char const *pf; +#else + char *pf; +#endif +/* FLAGS */ + int width, precision; + int justify; char pad; + int square, space, star_w, star_p, a_long, a_longlong; +}; + +/* signature of the functions */ +#ifdef __STDC__ +/* the floating point stuff */ + PRIVATE double pow_10(int); + PRIVATE int log_10(double); + PRIVATE double integral(double, double *); + PRIVATE char * numtoa(double, int, int, char **); + +/* for the format */ + PRIVATE void conv_flag(char *, struct DATA *); + PRIVATE void floating(struct DATA *, double); + PRIVATE void exponent(struct DATA *, double); + PRIVATE void decimal(struct DATA *, double); + PRIVATE void octal(struct DATA *, double); + PRIVATE void hexa(struct DATA *, double); + PRIVATE void strings(struct DATA *, char *); + +#else +/* the floating point stuff */ + PRIVATE double pow_10(); + PRIVATE int log_10(); + PRIVATE double integral(); + PRIVATE char * numtoa(); + +/* for the format */ + PRIVATE void conv_flag(); + PRIVATE void floating(); + PRIVATE void exponent(); + PRIVATE void decimal(); + PRIVATE void octal(); + PRIVATE void hexa(); + PRIVATE void strings(); +#endif + +/* those are defines specific to snprintf to hopefully + * make the code clearer :-) + */ +#define RIGHT 1 +#define LEFT 0 +#define NOT_FOUND -1 +#define FOUND 1 +#define MAX_FIELD 15 + +/* the conversion flags */ +#define isflag(c) ((c) == '#' || (c) == ' ' || \ + (c) == '*' || (c) == '+' || \ + (c) == '-' || (c) == '.' || \ + isdigit(c)) + +/* round off to the precision */ +#define ROUND(d, p) \ + (d < 0.) ? \ + d - pow_10(-(p)->precision) * 0.5 : \ + d + pow_10(-(p)->precision) * 0.5 + +/* set default precision */ +#define DEF_PREC(p) \ + if ((p)->precision == NOT_FOUND) \ + (p)->precision = 6 + +/* put a char */ +#define PUT_CHAR(c, p) \ + if ((p)->counter < (p)->length) { \ + *(p)->holder++ = (c); \ + (p)->counter++; \ + } + +#define PUT_PLUS(d, p) \ + if ((d) > 0. && (p)->justify == RIGHT) \ + PUT_CHAR('+', p) + +#define PUT_SPACE(d, p) \ + if ((p)->space == FOUND && (d) > 0.) \ + PUT_CHAR(' ', p) + +/* pad right */ +#define PAD_RIGHT(p) \ + if ((p)->width > 0 && (p)->justify != LEFT) \ + for (; (p)->width > 0; (p)->width--) \ + PUT_CHAR((p)->pad, p) + +/* pad left */ +#define PAD_LEFT(p) \ + if ((p)->width > 0 && (p)->justify == LEFT) \ + for (; (p)->width > 0; (p)->width--) \ + PUT_CHAR((p)->pad, p) + +/* if width and prec. in the args */ +#define STAR_ARGS(p) \ + if ((p)->star_w == FOUND) \ + (p)->width = va_arg(args, int); \ + if ((p)->star_p == FOUND) \ + (p)->precision = va_arg(args, int) + +#endif /* HAVE_VSNPRINTF */ diff --git a/src/lib/socket.c b/src/lib/socket.c new file mode 100644 index 0000000..970f4f4 --- /dev/null +++ b/src/lib/socket.c @@ -0,0 +1,390 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file socket.c + * @brief Functions for establishing and managing low level sockets. + * + * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org> + * @author Alan DeKok <aland@freeradius.org> + * + * @copyright 2015 The FreeRADIUS project + */ +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/socket.h> + +#ifdef HAVE_SYS_UN_H +/** Open a Unix socket + * + * @note If the file doesn't exist then errno will be set to ENOENT. + * + * The following code demonstrates using this function with a connection timeout: + @code {.c} + sockfd = fr_socket_client_unix(path, true); + if (sockfd < 0) { + fr_perror(); + exit(1); +} + if ((errno == EINPROGRESS) && (fr_socket_wait_for_connect(sockfd, timeout) < 0)) { + error: + fr_perror(); + close(sockfd); + goto error; +} +//Optionally, if blocking operation is required + if (fr_blocking(sockfd) < 0) goto error; + @endcode + * + * @param path to the file bound to the unix socket. + * @param async Whether to set the socket to nonblocking, allowing use of + * #fr_socket_wait_for_connect. + * @return socket FD on success, -1 on error. + */ +int fr_socket_client_unix(char const *path, bool async) +{ + int sockfd = -1; + size_t len; + socklen_t socklen; + struct sockaddr_un saremote; + + len = strlen(path); + if (len >= sizeof(saremote.sun_path)) { + fr_strerror_printf("Path too long, maximum length is %zu", sizeof(saremote.sun_path) - 1); + errno = EINVAL; + return -1; + } + + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd < 0) { + fr_strerror_printf("Failed creating UNIX socket: %s", fr_syserror(errno)); + return -1; + } + + if (async && (fr_nonblock(sockfd) < 0)) { + close(sockfd); + return -1; + } + + saremote.sun_family = AF_UNIX; + memcpy(saremote.sun_path, path, len + 1); /* SUN_LEN does strlen */ + + socklen = SUN_LEN(&saremote); + + /* + * Although we ignore SIGPIPE, some operating systems + * like BSD and OSX ignore the ignoring. + * + * Fortunately, those operating systems usually support + * SO_NOSIGPIPE, to prevent them raising the signal in + * the first place. + */ +#ifdef SO_NOSIGPIPE + { + int set = 1; + + setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)); + } +#endif + + if (connect(sockfd, (struct sockaddr *)&saremote, socklen) < 0) { + /* + * POSIX says the only time we will get this, + * is if the socket has been marked as + * nonblocking. This is not an error, the caller + * must check the state of errno, and wait for + * the connection to complete. + */ + if (errno == EINPROGRESS) return sockfd; + + close(sockfd); + fr_strerror_printf("Failed connecting to %s: %s", path, fr_syserror(errno)); + + return -1; + } + return sockfd; +} +#else +int fr_socket_client_unix(UNUSED char const *path, UNUSED bool async) +{ + fprintf(stderr, "Unix domain sockets not supported on this system"); + return -1; +} +#endif /* WITH_SYS_UN_H */ + +/** Establish a connected TCP socket + * + * The following code demonstrates using this function with a connection timeout: + @code {.c} + sockfd = fr_socket_client_tcp(NULL, ipaddr, port, true); + if (sockfd < 0) { + fr_perror(); + exit(1); +} + if ((errno == EINPROGRESS) && (fr_socket_wait_for_connect(sockfd, timeout) < 0)) { + error: + fr_perror(); + close(sockfd); + goto error; +} +//Optionally, if blocking operation is required + if (fr_blocking(sockfd) < 0) goto error; + @endcode + * + * @param src_ipaddr to bind socket to, may be NULL if socket is not bound to any specific + * address. + * @param dst_ipaddr Where to connect to. + * @param dst_port Where to connect to. + * @param async Whether to set the socket to nonblocking, allowing use of + * #fr_socket_wait_for_connect. + * @return FD on success, -1 on failure. + */ +int fr_socket_client_tcp(fr_ipaddr_t *src_ipaddr, fr_ipaddr_t *dst_ipaddr, uint16_t dst_port, bool async) +{ + int sockfd; + struct sockaddr_storage salocal; + socklen_t salen; + + if (!dst_ipaddr) return -1; + + sockfd = socket(dst_ipaddr->af, SOCK_STREAM, 0); + if (sockfd < 0) { + fr_strerror_printf("Error creating TCP socket: %s", fr_syserror(errno)); + return sockfd; + } + + if (async && (fr_nonblock(sockfd) < 0)) { + close(sockfd); + return -1; + } + + /* + * Allow the caller to bind us to a specific source IP. + */ + if (src_ipaddr && (src_ipaddr->af != AF_UNSPEC)) { + if (!fr_ipaddr2sockaddr(src_ipaddr, 0, &salocal, &salen)) { + close(sockfd); + return -1; + } + + if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) { + fr_strerror_printf("Failure binding to IP: %s", fr_syserror(errno)); + close(sockfd); + return -1; + } + } + + if (!fr_ipaddr2sockaddr(dst_ipaddr, dst_port, &salocal, &salen)) { + close(sockfd); + return -1; + } + + /* + * Although we ignore SIGPIPE, some operating systems + * like BSD and OSX ignore the ignoring. + * + * Fortunately, those operating systems usually support + * SO_NOSIGPIPE, to prevent them raising the signal in + * the first place. + */ +#ifdef SO_NOSIGPIPE + { + int set = 1; + + setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)); + } +#endif + + if (connect(sockfd, (struct sockaddr *) &salocal, salen) < 0) { + /* + * POSIX says the only time we will get this, + * is if the socket has been marked as + * nonblocking. This is not an error, the caller + * must check the state of errno, and wait for + * the connection to complete. + */ + if (errno == EINPROGRESS) return sockfd; + + fr_strerror_printf("Failed connecting socket: %s", fr_syserror(errno)); + close(sockfd); + return -1; + } + + return sockfd; +} + +/** Establish a connected UDP socket + * + * Connected UDP sockets can be used with write(), unlike unconnected sockets + * which must be used with sendto and recvfrom. + * + * The following code demonstrates using this function with a connection timeout: + @code {.c} + sockfd = fr_socket_client_udp(NULL, ipaddr, port, true); + if (sockfd < 0) { + fr_perror(); + exit(1); +} + if ((errno == EINPROGRESS) && (fr_socket_wait_for_connect(sockfd, timeout) < 0)) { + error: + fr_perror(); + close(sockfd); + goto error; +} +//Optionally, if blocking operation is required + if (fr_blocking(sockfd) < 0) goto error; + @endcode + * + * @param src_ipaddr to bind socket to, may be NULL if socket is not bound to any specific + * address. + * @param dst_ipaddr Where to send datagrams. + * @param dst_port Where to send datagrams. + * @param async Whether to set the socket to nonblocking, allowing use of + * #fr_socket_wait_for_connect. + * @return FD on success, -1 on failure. + */ +int fr_socket_client_udp(fr_ipaddr_t *src_ipaddr, fr_ipaddr_t *dst_ipaddr, uint16_t dst_port, bool async) +{ + int sockfd; + struct sockaddr_storage salocal; + socklen_t salen; + + if (!dst_ipaddr) return -1; + + sockfd = socket(dst_ipaddr->af, SOCK_DGRAM, 0); + if (sockfd < 0) { + fr_strerror_printf("Error creating UDP socket: %s", fr_syserror(errno)); + return sockfd; + } + + if (async && (fr_nonblock(sockfd) < 0)) { + close(sockfd); + return -1; + } + + /* + * Allow the caller to bind us to a specific source IP. + */ + if (src_ipaddr && (src_ipaddr->af != AF_UNSPEC)) { + if (!fr_ipaddr2sockaddr(src_ipaddr, 0, &salocal, &salen)) { + close(sockfd); + return -1; + } + + if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) { + fr_strerror_printf("Failure binding to IP: %s", fr_syserror(errno)); + close(sockfd); + return -1; + } + } + + if (!fr_ipaddr2sockaddr(dst_ipaddr, dst_port, &salocal, &salen)) { + close(sockfd); + return -1; + } + + /* + * Although we ignore SIGPIPE, some operating systems + * like BSD and OSX ignore the ignoring. + * + * Fortunately, those operating systems usually support + * SO_NOSIGPIPE, to prevent them raising the signal in + * the first place. + */ +#ifdef SO_NOSIGPIPE + { + int set = 1; + + setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)); + } +#endif + + if (connect(sockfd, (struct sockaddr *) &salocal, salen) < 0) { + /* + * POSIX says the only time we will get this, + * is if the socket has been marked as + * nonblocking. This is not an error, the caller + * must check the state of errno, and wait for + * the connection to complete. + */ + if (errno == EINPROGRESS) return sockfd; + + fr_strerror_printf("Failed connecting socket: %s", fr_syserror(errno)); + close(sockfd); + return -1; + } + + return sockfd; +} + +/** Wait for a socket to be connected, with an optional timeout + * + * @note On error the caller is expected to ``close(sockfd)``. + * + * @param sockfd the socket to wait on. + * @param timeout How long to wait for socket to open. + * @return 0 on success, -1 on connection error, -2 on timeout, -3 on select error. + */ +int fr_socket_wait_for_connect(int sockfd, struct timeval *timeout) +{ + int ret; + fd_set error_set; + fd_set write_set; /* POSIX says sockets are open when they become writeable */ + + FD_ZERO(&error_set); + FD_ZERO(&write_set); + + FD_SET(sockfd, &error_set); + FD_SET(sockfd, &write_set); + + /* Don't let signals mess up the select */ + do { + ret = select(sockfd + 1, NULL, &write_set, &error_set, timeout); + } while ((ret == -1) && (errno == EINTR)); + + switch (ret) { + case 1: /* ok (maybe) */ + { + int error; + socklen_t socklen = sizeof(error); + + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&error, &socklen)) { + fr_strerror_printf("Failed connecting socket: %s", fr_syserror(errno)); + return -1; + } + + if (FD_ISSET(sockfd, &error_set)) { + fr_strerror_printf("Failed connecting socket: Unknown error"); + return -1; + } + } + return 0; + + case 0: /* timeout */ + if (!fr_assert(timeout)) return -1; + fr_strerror_printf("Connection timed out after %" PRIu64"ms", + (timeout->tv_sec * (uint64_t)1000) + (timeout->tv_usec / 1000)); + return -2; + + case -1: /* select error */ + fr_strerror_printf("Failed waiting for connection: %s", fr_syserror(errno)); + return -3; + + default: + fr_assert(0); + return -1; + } +} diff --git a/src/lib/strlcat.c b/src/lib/strlcat.c new file mode 100644 index 0000000..b447206 --- /dev/null +++ b/src/lib/strlcat.c @@ -0,0 +1,67 @@ +/* + * strlcat.c Concatenate strings. + * + * Version: $Id$ + * + */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. + * + * Copyright 2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#ifndef HAVE_STRLCAT + +#include <freeradius-devel/missing.h> + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, char const *src, size_t siz) +{ + char *d = dst; + char const *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} + +#endif diff --git a/src/lib/strlcpy.c b/src/lib/strlcpy.c new file mode 100644 index 0000000..689df5d --- /dev/null +++ b/src/lib/strlcpy.c @@ -0,0 +1,63 @@ +/* + * strlcpy.c Copy strings. + * + * Version: $Id$ + * + */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. + * + * Copyright 2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#ifndef HAVE_STRLCPY + +#include <freeradius-devel/missing.h> + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, char const *src, size_t siz) +{ + char *d = dst; + char const *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + +#endif diff --git a/src/lib/talloc.c b/src/lib/talloc.c new file mode 100644 index 0000000..a2af16a --- /dev/null +++ b/src/lib/talloc.c @@ -0,0 +1,74 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** Functions which we wish were included in the standard talloc distribution + * + * @file src/lib/talloc.c + * + * @copyright 2017 The FreeRADIUS server project + * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org) + */ +RCSID("$Id$") + +#include <freeradius-devel/math.h> +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/talloc.h> + +/** Return a page aligned talloc memory array + * + * Because we can't intercept talloc's malloc() calls, we need to do some tricks + * in order to get the first allocation in the array page aligned, and to limit + * the size of the array to a multiple of the page size. + * + * The reason for wanting a page aligned talloc array, is it allows us to + * mprotect() the pages that belong to the array. + * + * Talloc chunks appear to be allocated within the protected region, so this should + * catch frees too. + * + * @param[in] ctx to allocate array memory in. + * @param[out] start The first aligned address in the array. + * @param[in] alignment What alignment the memory chunk should have. + * @param[in] size How big to make the array. Will be corrected to a multiple + * of the page size. The actual array size will be size + * rounded to a multiple of the (page_size), + page_size + * @return + * - A talloc chunk on success. + * - NULL on failure. + */ +TALLOC_CTX *talloc_aligned_array(TALLOC_CTX *ctx, void **start, size_t alignment, size_t size) +{ + size_t rounded; + size_t array_size; + void *next; + TALLOC_CTX *array; + + rounded = ROUND_UP(size, alignment); /* Round up to a multiple of the page size */ + if (rounded == 0) rounded = alignment; + + array_size = rounded + alignment; + array = talloc_array(ctx, uint8_t, array_size); /* Over allocate */ + if (!array) { + fr_strerror_printf("Out of memory"); + return NULL; + } + + next = (void *)ROUND_UP((uintptr_t)array, alignment); /* Round up address to the next multiple */ + *start = next; + + return array; +} + diff --git a/src/lib/tcp.c b/src/lib/tcp.c new file mode 100644 index 0000000..355277c --- /dev/null +++ b/src/lib/tcp.c @@ -0,0 +1,172 @@ +/* + * tcp.c TCP-specific functions. + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright (C) 2009 Dante http://dante.net + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#ifdef WITH_TCP + +RADIUS_PACKET *fr_tcp_recv(int sockfd, int flags) +{ + RADIUS_PACKET *packet = rad_alloc(NULL, false); + + if (!packet) return NULL; + + packet->sockfd = sockfd; + + if (fr_tcp_read_packet(packet, flags) != 1) { + rad_free(&packet); + return NULL; + } + + return packet; +} + + +/* + * Receives a packet, assuming that the RADIUS_PACKET structure + * has been filled out already. + * + * This ASSUMES that the packet is allocated && fields + * initialized. + * + * This ASSUMES that the socket is marked as O_NONBLOCK, which + * the function above does set, if your system supports it. + * + * Calling this function MAY change sockfd, + * if src_ipaddr.af == AF_UNSPEC. + */ +int fr_tcp_read_packet(RADIUS_PACKET *packet, int flags) +{ + ssize_t len; + + /* + * No data allocated. Read the 4-byte header into + * a temporary buffer. + */ + if (!packet->data) { + int packet_len; + + len = recv(packet->sockfd, packet->vector + packet->data_len, + 4 - packet->data_len, 0); + if (len == 0) return -2; /* clean close */ + +#ifdef ECONNRESET + if ((len < 0) && (errno == ECONNRESET)) { /* forced */ + return -2; + } +#endif + + if (len < 0) { + fr_strerror_printf("Error receiving packet: %s", + fr_syserror(errno)); + return -1; + } + + packet->data_len += len; + if (packet->data_len < 4) { /* want more data */ + return 0; + } + + packet_len = (packet->vector[2] << 8) | packet->vector[3]; + + if (packet_len < RADIUS_HDR_LEN) { + fr_strerror_printf("Discarding packet: Smaller than RFC minimum of 20 bytes"); + return -1; + } + + /* + * If the packet is too big, then the socket is bad. + */ + if (packet_len > MAX_PACKET_LEN) { + fr_strerror_printf("Discarding packet: Larger than RFC limitation of 4096 bytes"); + return -1; + } + + packet->data = talloc_array(packet, uint8_t, packet_len); + if (!packet->data) { + fr_strerror_printf("Out of memory"); + return -1; + } + + packet->data_len = packet_len; + packet->partial = 4; + memcpy(packet->data, packet->vector, 4); + } + + /* + * Try to read more data. + */ + len = recv(packet->sockfd, packet->data + packet->partial, + packet->data_len - packet->partial, 0); + if (len == 0) return -2; /* clean close */ + +#ifdef ECONNRESET + if ((len < 0) && (errno == ECONNRESET)) { /* forced */ + return -2; + } +#endif + + if (len < 0) { + fr_strerror_printf("Error receiving packet: %s", fr_syserror(errno)); + return -1; + } + + packet->partial += len; + + if (packet->partial < packet->data_len) { + return 0; + } + + /* + * See if it's a well-formed RADIUS packet. + */ + if (!rad_packet_ok(packet, flags, NULL)) { + return -1; + } + + /* + * Explicitly set the VP list to empty. + */ + packet->vps = NULL; + + if (fr_debug_lvl) { + char ip_buf[128], buffer[256]; + + if (packet->src_ipaddr.af != AF_UNSPEC) { + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + ip_buf, sizeof(ip_buf)); + snprintf(buffer, sizeof(buffer), "host %s port %d", + ip_buf, packet->src_port); + } else { + snprintf(buffer, sizeof(buffer), "socket %d", + packet->sockfd); + } + + } + + return 1; /* done reading the packet */ +} + +#endif /* WITH_TCP */ diff --git a/src/lib/token.c b/src/lib/token.c new file mode 100644 index 0000000..33b858b --- /dev/null +++ b/src/lib/token.c @@ -0,0 +1,481 @@ +/* + * token.c Read the next token from a string. + * Yes it's pretty primitive but effective. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#include <ctype.h> + +const FR_NAME_NUMBER fr_tokens[] = { + { "=~", T_OP_REG_EQ, }, /* order is important! */ + { "!~", T_OP_REG_NE, }, + { "{", T_LCBRACE, }, + { "}", T_RCBRACE, }, + { "(", T_LBRACE, }, + { ")", T_RBRACE, }, + { ",", T_COMMA, }, + { "++", T_OP_INCRM, }, + { "+=", T_OP_ADD, }, + { "-=", T_OP_SUB, }, + { ":=", T_OP_SET, }, + { "=*", T_OP_CMP_TRUE, }, + { "!*", T_OP_CMP_FALSE, }, + { "==", T_OP_CMP_EQ, }, + { "^=", T_OP_PREPEND, }, + { "=", T_OP_EQ, }, + { "!=", T_OP_NE, }, + { ">=", T_OP_GE, }, + { ">", T_OP_GT, }, + { "<=", T_OP_LE, }, + { "<", T_OP_LT, }, + { "#", T_HASH, }, + { ";", T_SEMICOLON, }, + { NULL, 0, }, +}; + +const bool fr_assignment_op[] = { + false, /* invalid token */ + false, /* end of line */ + false, /* { */ + false, /* } */ + false, /* ( */ + false, /* ) 5 */ + false, /* , */ + false, /* ; */ + + true, /* ++ */ + true, /* += */ + true, /* -= 10 */ + true, /* := */ + true, /* = */ + false, /* != */ + false, /* >= */ + false, /* > 15 */ + false, /* <= */ + false, /* < */ + false, /* =~ */ + false, /* !~ */ + false, /* =* 20 */ + false, /* !* */ + false, /* == */ + true, /* ^= */ + false, /* # */ + false, /* bare word 25 */ + false, /* "foo" */ + false, /* 'foo' */ + false, /* `foo` */ + false +}; + +const bool fr_equality_op[] = { + false, /* invalid token */ + false, /* end of line */ + false, /* { */ + false, /* } */ + false, /* ( */ + false, /* ) 5 */ + false, /* , */ + false, /* ; */ + + false, /* ++ */ + false, /* += */ + false, /* -= 10 */ + false, /* := */ + false, /* = */ + true, /* != */ + true, /* >= */ + true, /* > 15 */ + true, /* <= */ + true, /* < */ + true, /* =~ */ + true, /* !~ */ + true, /* =* 20 */ + true, /* !* */ + true, /* == */ + false, /* ^= */ + false, /* # */ + false, /* bare word 25 */ + false, /* "foo" */ + false, /* 'foo' */ + false, /* `foo` */ + false +}; + +const bool fr_str_tok[] = { + false, /* invalid token */ + false, /* end of line */ + false, /* { */ + false, /* } */ + false, /* ( */ + false, /* ) 5 */ + false, /* , */ + false, /* ; */ + + false, /* ++ */ + false, /* += */ + false, /* -= 10 */ + false, /* := */ + false, /* = */ + false, /* != */ + false, /* >= */ + false, /* > 15 */ + false, /* <= */ + false, /* < */ + false, /* =~ */ + false, /* !~ */ + false, /* =* 20 */ + false, /* !* */ + false, /* == */ + false, /* ^= */ + false, /* # */ + true, /* bare word 25 */ + true, /* "foo" */ + true, /* 'foo' */ + true, /* `foo` */ + false +}; + +/* + * This works only as long as special tokens + * are max. 2 characters, but it's fast. + */ +#define TOKEN_MATCH(bptr, tptr) \ + ( (tptr)[0] == (bptr)[0] && \ + ((tptr)[1] == (bptr)[1] || (tptr)[1] == 0)) + +/* + * Read a word from a buffer and advance pointer. + * This function knows about escapes and quotes. + * + * At end-of-line, buf[0] is set to '\0'. + * Returns 0 or special token value. + */ +static FR_TOKEN getthing(char const **ptr, char *buf, int buflen, bool tok, + FR_NAME_NUMBER const *tokenlist, bool unescape) +{ + char *s; + char const *p; + char quote; + bool end = false; + unsigned int x; + FR_NAME_NUMBER const *t; + FR_TOKEN rcode; + + buf[0] = '\0'; + + /* Skip whitespace */ + p = *ptr; + + while (*p && isspace((uint8_t) *p)) p++; + + if (!*p) { + *ptr = p; + return T_EOL; + } + + /* + * Might be a 1 or 2 character token. + */ + if (tok) for (t = tokenlist; t->name; t++) { + if (TOKEN_MATCH(p, t->name)) { + strcpy(buf, t->name); + p += strlen(t->name); + + rcode = t->number; + goto done; + } + } + + /* Read word. */ + quote = '\0'; + switch (*p) { + default: + rcode = T_BARE_WORD; + break; + + case '\'': + rcode = T_SINGLE_QUOTED_STRING; + break; + + case '"': + rcode = T_DOUBLE_QUOTED_STRING; + break; + + case '`': + rcode = T_BACK_QUOTED_STRING; + break; + } + + if (rcode != T_BARE_WORD) { + quote = *p; + end = false; + p++; + } + s = buf; + + while (*p && buflen-- > 1) { + /* + * We're looking for strings. Stop on spaces, or + * (if given a token list), on a token, or on a + * comma. + */ + if (!quote) { + if (isspace((uint8_t) *p)) { + break; + } + + if (tok) { + for (t = tokenlist; t->name; t++) { + if (TOKEN_MATCH(p, t->name)) { + *s++ = 0; + goto done; + } + } + } + if (*p == ',') break; + + /* + * Copy the character over. + */ + *s++ = *p++; + continue; + } /* else there was a quotation character */ + + /* + * Un-escaped quote character. We're done. + */ + if (*p == quote) { + end = true; + p++; + break; + } + + /* + * Everything but backslash gets copied over. + */ + if (*p != '\\') { + *s++ = *p++; + continue; + } + + /* + * There's nothing after the backslash, it's an error. + */ + if (!p[1]) { + fr_strerror_printf("Unterminated string"); + return T_INVALID; + } + + if (unescape) { + p++; + + switch (*p) { + case 'r': + *s++ = '\r'; + break; + case 'n': + *s++ = '\n'; + break; + case 't': + *s++ = '\t'; + break; + + default: + if (*p >= '0' && *p <= '9' && + sscanf(p, "%3o", &x) == 1) { + *s++ = x; + p += 2; + } else + *s++ = *p; + break; + } + p++; + + } else { + /* + * Convert backslash-quote to quote, but + * leave everything else alone. + */ + if (p[1] == quote) { /* convert '\'' --> ' */ + p++; + } else { + if (buflen < 2) { + fr_strerror_printf("Truncated input"); + return T_INVALID; + } + + *(s++) = *(p++); + } + *(s++) = *(p++); + } + } + + *s++ = 0; + + if (quote && !end) { + fr_strerror_printf("Unterminated string"); + return T_INVALID; + } + +done: + /* Skip whitespace again. */ + while (*p && isspace((uint8_t) *p)) p++; + + *ptr = p; + + return rcode; +} + +/* + * Read a "word" - this means we don't honor + * tokens as delimiters. + */ +int getword(char const **ptr, char *buf, int buflen, bool unescape) +{ + return getthing(ptr, buf, buflen, false, fr_tokens, unescape) == T_EOL ? 0 : 1; +} + + +/* + * Read the next word, use tokens as delimiters. + */ +FR_TOKEN gettoken(char const **ptr, char *buf, int buflen, bool unescape) +{ + return getthing(ptr, buf, buflen, true, fr_tokens, unescape); +} + +/* + * Expect an operator. + */ +FR_TOKEN getop(char const **ptr) +{ + char op[3]; + FR_TOKEN rcode; + + rcode = getthing(ptr, op, sizeof(op), true, fr_tokens, false); + if (!fr_assignment_op[rcode] && !fr_equality_op[rcode]) { + fr_strerror_printf("Expected operator"); + return T_INVALID; + } + return rcode; +} + +/* + * Expect a string. + */ +FR_TOKEN getstring(char const **ptr, char *buf, int buflen, bool unescape) +{ + char const *p; + + if (!ptr || !*ptr || !buf) return T_INVALID; + + p = *ptr; + + while (*p && (isspace((uint8_t)*p))) p++; + + *ptr = p; + + if ((*p == '"') || (*p == '\'') || (*p == '`')) { + return gettoken(ptr, buf, buflen, unescape); + } + + return getthing(ptr, buf, buflen, false, fr_tokens, unescape); +} + +/* + * Convert a string to an integer + */ +int fr_str2int(FR_NAME_NUMBER const *table, char const *name, int def) +{ + FR_NAME_NUMBER const *this; + + if (!name) { + return def; + } + + for (this = table; this->name != NULL; this++) { + if (strcasecmp(this->name, name) == 0) { + return this->number; + } + } + + return def; +} + +/* + * Convert a string matching part of name to an integer. + */ +int fr_substr2int(FR_NAME_NUMBER const *table, char const *name, int def, int len) +{ + FR_NAME_NUMBER const *this; + size_t max; + + if (!name) { + return def; + } + + for (this = table; this->name != NULL; this++) { + size_t tlen; + + tlen = strlen(this->name); + + /* + * Don't match "request" to user input "req". + */ + if ((len > 0) && (len < (int) tlen)) continue; + + /* + * Match up to the length of the table entry if len is < 0. + */ + max = (len < 0) ? tlen : (unsigned)len; + + if (strncasecmp(this->name, name, max) == 0) { + return this->number; + } + } + + return def; +} + +/* + * Convert an integer to a string. + */ +char const *fr_int2str(FR_NAME_NUMBER const *table, int number, + char const *def) +{ + FR_NAME_NUMBER const *this; + + for (this = table; this->name != NULL; this++) { + if (this->number == number) { + return this->name; + } + } + + return def; +} + +char const *fr_token_name(int token) +{ + return fr_int2str(fr_tokens, token, "???"); +} diff --git a/src/lib/udpfromto.c b/src/lib/udpfromto.c new file mode 100644 index 0000000..d54d8a6 --- /dev/null +++ b/src/lib/udpfromto.c @@ -0,0 +1,579 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file udpfromto.c + * @brief Like recvfrom, but also stores the destination IP address. Useful on multihomed hosts. + * + * @copyright 2007 Alan DeKok <aland@deployingradius.com> + * @copyright 2002 Miquel van Smoorenburg + */ +RCSID("$Id$") + +#include <freeradius-devel/udpfromto.h> + +#ifdef WITH_UDPFROMTO + +#ifdef HAVE_SYS_UIO_H +# include <sys/uio.h> +#endif + +#include <fcntl.h> + +/* + * More portability idiocy + * Mac OSX Lion doesn't define SOL_IP. But IPPROTO_IP works. + */ +#ifndef SOL_IP +# define SOL_IP IPPROTO_IP +#endif + +/* + * glibc 2.4 and uClibc 0.9.29 introduce IPV6_RECVPKTINFO etc. and + * change IPV6_PKTINFO This is only supported in Linux kernel >= + * 2.6.14 + * + * This is only an approximation because the kernel version that libc + * was compiled against could be older or newer than the one being + * run. But this should not be a problem -- we just keep using the + * old kernel interface. + */ +#ifdef __linux__ +# ifdef IPV6_RECVPKTINFO +# include <linux/version.h> +# if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) +# ifdef IPV6_2292PKTINFO +# undef IPV6_RECVPKTINFO +# undef IPV6_PKTINFO +# define IPV6_RECVPKTINFO IPV6_2292PKTINFO +# define IPV6_PKTINFO IPV6_2292PKTINFO +# endif +# endif +/* Fall back to the legacy socket option if IPV6_RECVPKTINFO isn't defined */ +# elif defined(IPV6_2292PKTINFO) +# define IPV6_RECVPKTINFO IPV6_2292PKTINFO +# endif +#else + +/* + * For everything that's not Linux we assume RFC 3542 compliance + * - setsockopt() takes IPV6_RECVPKTINFO + * - cmsg_type is IPV6_PKTINFO (in sendmsg, recvmsg) + * + * If we don't have IPV6_RECVPKTINFO defined but do have IPV6_PKTINFO + * defined, chances are the API is RFC2292 compliant and we need to use + * IPV6_PKTINFO for both. + */ +# if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) +# define IPV6_RECVPKTINFO IPV6_PKTINFO + +/* + * Ensure IPV6_RECVPKTINFO is not defined somehow if we have we + * don't have IPV6_PKTINFO. + */ +# elif !defined(IPV6_PKTINFO) +# undef IPV6_RECVPKTINFO +# endif +#endif + +int udpfromto_init(int s) +{ + int proto, flag = 0, opt = 1; + struct sockaddr_storage si; + socklen_t si_len = sizeof(si); + + errno = ENOSYS; + + /* + * Clang analyzer doesn't see that getsockname initialises + * the memory passed to it. + */ +#ifdef __clang_analyzer__ + memset(&si, 0, sizeof(si)); +#endif + + if (getsockname(s, (struct sockaddr *) &si, &si_len) < 0) { + return -1; + } + + if (si.ss_family == AF_INET) { +#ifdef HAVE_IP_PKTINFO + /* + * Linux + */ + proto = SOL_IP; + flag = IP_PKTINFO; +#else +# ifdef IP_RECVDSTADDR + + /* + * Set the IP_RECVDSTADDR option (BSD). Note: + * IP_RECVDSTADDR == IP_SENDSRCADDR + */ + proto = IPPROTO_IP; + flag = IP_RECVDSTADDR; +# else + return -1; +# endif +#endif + +#if defined(AF_INET6) && defined(IPV6_PKTINFO) + } else if (si.ss_family == AF_INET6) { + /* + * This should actually be standard IPv6 + */ + proto = IPPROTO_IPV6; + + /* + * Work around Linux-specific hackery. + */ + flag = IPV6_RECVPKTINFO; + } else { +#endif + + /* + * Unknown AF. Return an error if possible. + */ +# ifdef EPROTONOSUPPORT + errno = EPROTONOSUPPORT; +# endif + return -1; + } + + return setsockopt(s, proto, flag, &opt, sizeof(opt)); +} + +int recvfromto(int s, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen, + struct sockaddr *to, socklen_t *tolen) +{ + struct msghdr msgh; + struct cmsghdr *cmsg; + struct iovec iov; + char cbuf[256]; + int err; + struct sockaddr_storage si; + socklen_t si_len = sizeof(si); + +#if !defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR) && !defined(IPV6_PKTINFO) + /* + * If the recvmsg() flags aren't defined, fall back to + * using recvfrom(). + */ + to = NULL: +#endif + + /* + * Catch the case where the caller passes invalid arguments. + */ + if (!to || !tolen) return recvfrom(s, buf, len, flags, from, fromlen); + + /* + * Clang analyzer doesn't see that getsockname initialises + * the memory passed to it. + */ +#ifdef __clang_analyzer__ + memset(&si, 0, sizeof(si)); +#endif + + /* + * recvmsg doesn't provide sin_port so we have to + * retrieve it using getsockname(). + */ + if (getsockname(s, (struct sockaddr *)&si, &si_len) < 0) { + return -1; + } + + /* + * Initialize the 'to' address. It may be INADDR_ANY here, + * with a more specific address given by recvmsg(), below. + */ + if (si.ss_family == AF_INET) { +#if !defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR) + return recvfrom(s, buf, len, flags, from, fromlen); +#else + struct sockaddr_in *dst = (struct sockaddr_in *) to; + struct sockaddr_in *src = (struct sockaddr_in *) &si; + + if (*tolen < sizeof(*dst)) { + errno = EINVAL; + return -1; + } + *tolen = sizeof(*dst); + *dst = *src; +#endif + } + +#ifdef AF_INET6 + else if (si.ss_family == AF_INET6) { +#if !defined(IPV6_PKTINFO) + return recvfrom(s, buf, len, flags, from, fromlen); +#else + struct sockaddr_in6 *dst = (struct sockaddr_in6 *) to; + struct sockaddr_in6 *src = (struct sockaddr_in6 *) &si; + + if (*tolen < sizeof(*dst)) { + errno = EINVAL; + return -1; + } + *tolen = sizeof(*dst); + *dst = *src; +#endif + } +#endif + /* + * Unknown address family. + */ + else { + errno = EINVAL; + return -1; + } + + /* Set up iov and msgh structures. */ + memset(&cbuf, 0, sizeof(cbuf)); + memset(&msgh, 0, sizeof(struct msghdr)); + iov.iov_base = buf; + iov.iov_len = len; + msgh.msg_control = cbuf; + msgh.msg_controllen = sizeof(cbuf); + msgh.msg_name = from; + msgh.msg_namelen = fromlen ? *fromlen : 0; + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_flags = 0; + + /* Receive one packet. */ + if ((err = recvmsg(s, &msgh, flags)) < 0) { + return err; + } + + if (fromlen) *fromlen = msgh.msg_namelen; + + /* Process auxiliary received data in msgh */ + for (cmsg = CMSG_FIRSTHDR(&msgh); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msgh,cmsg)) { + +#ifdef IP_PKTINFO + if ((cmsg->cmsg_level == SOL_IP) && + (cmsg->cmsg_type == IP_PKTINFO)) { + struct in_pktinfo *i = + (struct in_pktinfo *) CMSG_DATA(cmsg); + ((struct sockaddr_in *)to)->sin_addr = i->ipi_addr; + *tolen = sizeof(struct sockaddr_in); + break; + } +#endif + +#ifdef IP_RECVDSTADDR + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_RECVDSTADDR)) { + struct in_addr *i = (struct in_addr *) CMSG_DATA(cmsg); + ((struct sockaddr_in *)to)->sin_addr = *i; + *tolen = sizeof(struct sockaddr_in); + break; + } +#endif + +#ifdef IPV6_PKTINFO + if ((cmsg->cmsg_level == IPPROTO_IPV6) && + (cmsg->cmsg_type == IPV6_PKTINFO)) { + struct in6_pktinfo *i = + (struct in6_pktinfo *) CMSG_DATA(cmsg); + ((struct sockaddr_in6 *)to)->sin6_addr = i->ipi6_addr; + *tolen = sizeof(struct sockaddr_in6); + break; + } +#endif + } + + return err; +} + +int sendfromto(int s, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t fromlen, + struct sockaddr *to, socklen_t tolen) +{ + struct msghdr msgh; + struct iovec iov; + char cbuf[256]; + + /* + * Unknown address family, die. + */ + if (from && (from->sa_family != AF_INET) && (from->sa_family != AF_INET6)) { + errno = EINVAL; + return -1; + } + +#ifdef __FreeBSD__ + /* + * FreeBSD is extra pedantic about the use of IP_SENDSRCADDR, + * and sendmsg will fail with EINVAL if IP_SENDSRCADDR is used + * with a socket which is bound to something other than + * INADDR_ANY + */ + struct sockaddr bound; + socklen_t bound_len = sizeof(bound); + + if (getsockname(s, &bound, &bound_len) < 0) { + return -1; + } + + switch (bound.sa_family) { + case AF_INET: + if (((struct sockaddr_in *) &bound)->sin_addr.s_addr != INADDR_ANY) { + from = NULL; + } + break; + + case AF_INET6: + if (!IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *) &bound)->sin6_addr)) { + from = NULL; + } + break; + } +#endif /* !__FreeBSD__ */ + + /* + * If the sendmsg() flags aren't defined, fall back to + * using sendto(). These flags are defined on FreeBSD, + * but laying it out this way simplifies the look of the + * code. + */ +# if !defined(IP_PKTINFO) && !defined(IP_SENDSRCADDR) + if (from && from->sa_family == AF_INET) { + from = NULL; + } +# endif + +# if !defined(IPV6_PKTINFO) + if (from && from->sa_family == AF_INET6) { + from = NULL; + } +# endif + + /* + * No "from", just use regular sendto. + */ + if (!from || (fromlen == 0)) { + return sendto(s, buf, len, flags, to, tolen); + } + + /* Set up control buffer iov and msgh structures. */ + memset(&cbuf, 0, sizeof(cbuf)); + memset(&msgh, 0, sizeof(msgh)); + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = len; + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_name = to; + msgh.msg_namelen = tolen; + +# if defined(IP_PKTINFO) || defined(IP_SENDSRCADDR) + if (from->sa_family == AF_INET) { + struct sockaddr_in *s4 = (struct sockaddr_in *) from; + +# ifdef IP_PKTINFO + struct cmsghdr *cmsg; + struct in_pktinfo *pkt; + + msgh.msg_control = cbuf; + msgh.msg_controllen = CMSG_SPACE(sizeof(*pkt)); + + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = SOL_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(*pkt)); + + pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); + memset(pkt, 0, sizeof(*pkt)); + pkt->ipi_spec_dst = s4->sin_addr; + +# elif defined(IP_SENDSRCADDR) + struct cmsghdr *cmsg; + struct in_addr *in; + + msgh.msg_control = cbuf; + msgh.msg_controllen = CMSG_SPACE(sizeof(*in)); + + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_SENDSRCADDR; + cmsg->cmsg_len = CMSG_LEN(sizeof(*in)); + + in = (struct in_addr *) CMSG_DATA(cmsg); + *in = s4->sin_addr; +# endif + } +#endif + +# if defined(IPV6_PKTINFO) + if (from->sa_family == AF_INET6) { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) from; + + struct cmsghdr *cmsg; + struct in6_pktinfo *pkt; + + msgh.msg_control = cbuf; + msgh.msg_controllen = CMSG_SPACE(sizeof(*pkt)); + + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(*pkt)); + + pkt = (struct in6_pktinfo *) CMSG_DATA(cmsg); + memset(pkt, 0, sizeof(*pkt)); + pkt->ipi6_addr = s6->sin6_addr; + } +# endif /* IPV6_PKTINFO */ + + return sendmsg(s, &msgh, flags); +} + + +#ifdef TESTING +/* + * Small test program to test recvfromto/sendfromto + * + * use a virtual IP address as first argument to test + * + * reply packet should originate from virtual IP and not + * from the default interface the alias is bound to + */ +# include <sys/wait.h> + +# define DEF_PORT 20000 /* default port to listen on */ +# define DESTIP "127.0.0.1" /* send packet to localhost per default */ +# define TESTSTRING "foo" /* what to send */ +# define TESTLEN 4 /* 4 bytes */ + +int main(int argc, char **argv) +{ + struct sockaddr_in from, to, in; + char buf[TESTLEN]; + char *destip = DESTIP; + uint16_t port = DEF_PORT; + int n, server_socket, client_socket, fl, tl, pid; + + if (argc > 1) destip = argv[1]; + if (argc > 2) port = atoi(argv[2]); + + in.sin_family = AF_INET; + in.sin_addr.s_addr = INADDR_ANY; + in.sin_port = htons(port); + fl = tl = sizeof(struct sockaddr_in); + memset(&from, 0, sizeof(from)); + memset(&to, 0, sizeof(to)); + + switch (pid = fork()) { + case -1: + perror("fork"); + return 0; + case 0: + /* child */ + usleep(100000); + goto client; + } + + /* parent: server */ + server_socket = socket(PF_INET, SOCK_DGRAM, 0); + if (udpfromto_init(server_socket) != 0) { + perror("udpfromto_init\n"); + waitpid(pid, NULL, WNOHANG); + return 0; + } + + if (bind(server_socket, (struct sockaddr *)&in, sizeof(in)) < 0) { + perror("server: bind"); + waitpid(pid, NULL, WNOHANG); + return 0; + } + + printf("server: waiting for packets on INADDR_ANY:%d\n", port); + if ((n = recvfromto(server_socket, buf, sizeof(buf), 0, + (struct sockaddr *)&from, &fl, + (struct sockaddr *)&to, &tl)) < 0) { + perror("server: recvfromto"); + waitpid(pid, NULL, WNOHANG); + return 0; + } + + printf("server: received a packet of %d bytes [%s] ", n, buf); + printf("(src ip:port %s:%d ", + inet_ntoa(from.sin_addr), ntohs(from.sin_port)); + printf(" dst ip:port %s:%d)\n", + inet_ntoa(to.sin_addr), ntohs(to.sin_port)); + + printf("server: replying from address packet was received on to source address\n"); + + if ((n = sendfromto(server_socket, buf, n, 0, + (struct sockaddr *)&to, tl, + (struct sockaddr *)&from, fl)) < 0) { + perror("server: sendfromto"); + } + + waitpid(pid, NULL, 0); + return 0; + +client: + close(server_socket); + client_socket = socket(PF_INET, SOCK_DGRAM, 0); + if (udpfromto_init(client_socket) != 0) { + perror("udpfromto_init"); + fr_exit_now(0); + } + /* bind client on different port */ + in.sin_port = htons(port+1); + if (bind(client_socket, (struct sockaddr *)&in, sizeof(in)) < 0) { + perror("client: bind"); + fr_exit_now(0); + } + + in.sin_port = htons(port); + in.sin_addr.s_addr = inet_addr(destip); + + printf("client: sending packet to %s:%d\n", destip, port); + if (sendto(client_socket, TESTSTRING, TESTLEN, 0, + (struct sockaddr *)&in, sizeof(in)) < 0) { + perror("client: sendto"); + fr_exit_now(0); + } + + printf("client: waiting for reply from server on INADDR_ANY:%d\n", port+1); + + if ((n = recvfromto(client_socket, buf, sizeof(buf), 0, + (struct sockaddr *)&from, &fl, + (struct sockaddr *)&to, &tl)) < 0) { + perror("client: recvfromto"); + fr_exit_now(0); + } + + printf("client: received a packet of %d bytes [%s] ", n, buf); + printf("(src ip:port %s:%d", + inet_ntoa(from.sin_addr), ntohs(from.sin_port)); + printf(" dst ip:port %s:%d)\n", + inet_ntoa(to.sin_addr), ntohs(to.sin_port)); + + fr_exit_now(0); +} + +#endif /* TESTING */ +#endif /* WITH_UDPFROMTO */ diff --git a/src/lib/value.c b/src/lib/value.c new file mode 100644 index 0000000..aa49897 --- /dev/null +++ b/src/lib/value.c @@ -0,0 +1,2013 @@ +/* + * value.c Functions to handle value_data_t + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2014 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> +#include <ctype.h> + +/** Compare two values + * + * @param[in] a_type of data to compare. + * @param[in] a_len of data to compare. + * @param[in] a Value to compare. + * @param[in] b_type of data to compare. + * @param[in] b_len of data to compare. + * @param[in] b Value to compare. + * @return -1 if a is less than b, 0 if both are equal, 1 if a is more than b, < -1 on error. + */ +int value_data_cmp(PW_TYPE a_type, value_data_t const *a, size_t a_len, + PW_TYPE b_type, value_data_t const *b, size_t b_len) +{ + int compare = 0; + + if (a_type != b_type) { + fr_strerror_printf("Can't compare values of different types"); + return -2; + } + + /* + * After doing the previous check for special comparisons, + * do the per-type comparison here. + */ + switch (a_type) { + case PW_TYPE_ABINARY: + case PW_TYPE_OCTETS: + case PW_TYPE_STRING: /* We use memcmp to be \0 safe */ + { + size_t length; + + if (a_len < b_len) { + length = a_len; + } else { + length = b_len; + } + + if (length) { + compare = memcmp(a->octets, b->octets, length); + if (compare != 0) break; + } + + /* + * Contents are the same. The return code + * is therefore the difference in lengths. + * + * i.e. "0x00" is smaller than "0x0000" + */ + compare = a_len - b_len; + } + break; + + /* + * Short-hand for simplicity. + */ +#define CHECK(_type) if (a->_type < b->_type) { compare = -1; \ + } else if (a->_type > b->_type) { compare = +1; } + + case PW_TYPE_BOOLEAN: /* this isn't a RADIUS type, and shouldn't really ever be used */ + case PW_TYPE_BYTE: + CHECK(byte); + break; + + + case PW_TYPE_SHORT: + CHECK(ushort); + break; + + case PW_TYPE_DATE: + CHECK(date); + break; + + case PW_TYPE_INTEGER: + CHECK(integer); + break; + + case PW_TYPE_SIGNED: + CHECK(sinteger); + break; + + case PW_TYPE_INTEGER64: + CHECK(integer64); + break; + + case PW_TYPE_ETHERNET: + compare = memcmp(a->ether, b->ether, sizeof(a->ether)); + break; + + case PW_TYPE_IPV4_ADDR: { + uint32_t a_int, b_int; + + a_int = ntohl(a->ipaddr.s_addr); + b_int = ntohl(b->ipaddr.s_addr); + if (a_int < b_int) { + compare = -1; + } else if (a_int > b_int) { + compare = +1; + } + } + break; + + case PW_TYPE_IPV6_ADDR: + compare = memcmp(&a->ipv6addr, &b->ipv6addr, sizeof(a->ipv6addr)); + break; + + case PW_TYPE_IPV6_PREFIX: + compare = memcmp(a->ipv6prefix, b->ipv6prefix, sizeof(a->ipv6prefix)); + break; + + case PW_TYPE_IPV4_PREFIX: + compare = memcmp(a->ipv4prefix, b->ipv4prefix, sizeof(a->ipv4prefix)); + break; + + case PW_TYPE_IFID: + compare = memcmp(a->ifid, b->ifid, sizeof(a->ifid)); + break; + + /* + * Na of the types below should be in the REQUEST + */ + case PW_TYPE_INVALID: /* We should never see these */ + case PW_TYPE_COMBO_IP_ADDR: /* This should have been converted into IPADDR/IPV6ADDR */ + case PW_TYPE_COMBO_IP_PREFIX: /* This should have been converted into IPADDR/IPV6ADDR */ + case PW_TYPE_TLV: + case PW_TYPE_EXTENDED: + case PW_TYPE_LONG_EXTENDED: + case PW_TYPE_EVS: + case PW_TYPE_VSA: + case PW_TYPE_TIMEVAL: + case PW_TYPE_MAX: + fr_assert(0); /* unknown type */ + return -2; + + /* + * Do NOT add a default here, as new types are added + * static analysis will warn us they're not handled + */ + } + + if (compare > 0) { + return 1; + } else if (compare < 0) { + return -1; + } + return 0; +} + +/* + * We leverage the fact that IPv4 and IPv6 prefixes both + * have the same format: + * + * reserved, prefix-len, data... + */ +static int value_data_cidr_cmp_op(FR_TOKEN op, int bytes, + uint8_t a_net, uint8_t const *a, + uint8_t b_net, uint8_t const *b) +{ + int i, common; + uint32_t mask; + + /* + * Handle the case of netmasks being identical. + */ + if (a_net == b_net) { + int compare; + + compare = memcmp(a, b, bytes); + + /* + * If they're identical return true for + * identical. + */ + if ((compare == 0) && + ((op == T_OP_CMP_EQ) || + (op == T_OP_LE) || + (op == T_OP_GE))) { + return true; + } + + /* + * Everything else returns false. + * + * 10/8 == 24/8 --> false + * 10/8 <= 24/8 --> false + * 10/8 >= 24/8 --> false + */ + return false; + } + + /* + * Netmasks are different. That limits the + * possible results, based on the operator. + */ + switch (op) { + case T_OP_CMP_EQ: + return false; + + case T_OP_NE: + return true; + + case T_OP_LE: + case T_OP_LT: /* 192/8 < 192.168/16 --> false */ + if (a_net < b_net) { + return false; + } + break; + + case T_OP_GE: + case T_OP_GT: /* 192/16 > 192.168/8 --> false */ + if (a_net > b_net) { + return false; + } + break; + + default: + return false; + } + + if (a_net < b_net) { + common = a_net; + } else { + common = b_net; + } + + /* + * Do the check byte by byte. If the bytes are + * identical, it MAY be a match. If they're different, + * it is NOT a match. + */ + i = 0; + while (i < bytes) { + /* + * All leading bytes are identical. + */ + if (common == 0) return true; + + /* + * Doing bitmasks takes more work. + */ + if (common < 8) break; + + if (a[i] != b[i]) return false; + + common -= 8; + i++; + continue; + } + + mask = 1; + mask <<= (8 - common); + mask--; + mask = ~mask; + + if ((a[i] & mask) == ((b[i] & mask))) { + return true; + } + + return false; +} + +/** Compare two attributes using an operator + * + * @param[in] op to use in comparison. + * @param[in] a_type of data to compare. + * @param[in] a_len of data to compare. + * @param[in] a Value to compare. + * @param[in] b_type of data to compare. + * @param[in] b_len of data to compare. + * @param[in] b Value to compare. + * @return 1 if true, 0 if false, -1 on error. + */ +int value_data_cmp_op(FR_TOKEN op, + PW_TYPE a_type, value_data_t const *a, size_t a_len, + PW_TYPE b_type, value_data_t const *b, size_t b_len) +{ + int compare = 0; + + if (!a || !b) return -1; + + switch (a_type) { + case PW_TYPE_IPV4_ADDR: + switch (b_type) { + case PW_TYPE_IPV4_ADDR: /* IPv4 and IPv4 */ + goto cmp; + + case PW_TYPE_IPV4_PREFIX: /* IPv4 and IPv4 Prefix */ + return value_data_cidr_cmp_op(op, 4, 32, (uint8_t const *) &a->ipaddr, + b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix[2]); + + default: + fr_strerror_printf("Cannot compare IPv4 with IPv6 address"); + return -1; + } + + case PW_TYPE_IPV4_PREFIX: /* IPv4 and IPv4 Prefix */ + switch (b_type) { + case PW_TYPE_IPV4_ADDR: + return value_data_cidr_cmp_op(op, 4, a->ipv4prefix[1], + (uint8_t const *) &a->ipv4prefix[2], + 32, (uint8_t const *) &b->ipaddr); + + case PW_TYPE_IPV4_PREFIX: /* IPv4 Prefix and IPv4 Prefix */ + return value_data_cidr_cmp_op(op, 4, a->ipv4prefix[1], + (uint8_t const *) &a->ipv4prefix[2], + b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix[2]); + + default: + fr_strerror_printf("Cannot compare IPv4 with IPv6 address"); + return -1; + } + + case PW_TYPE_IPV6_ADDR: + switch (b_type) { + case PW_TYPE_IPV6_ADDR: /* IPv6 and IPv6 */ + goto cmp; + + case PW_TYPE_IPV6_PREFIX: /* IPv6 and IPv6 Preifx */ + return value_data_cidr_cmp_op(op, 16, 128, (uint8_t const *) &a->ipv6addr, + b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix[2]); + + default: + fr_strerror_printf("Cannot compare IPv6 with IPv4 address"); + return -1; + } + + case PW_TYPE_IPV6_PREFIX: + switch (b_type) { + case PW_TYPE_IPV6_ADDR: /* IPv6 Prefix and IPv6 */ + return value_data_cidr_cmp_op(op, 16, a->ipv6prefix[1], + (uint8_t const *) &a->ipv6prefix[2], + 128, (uint8_t const *) &b->ipv6addr); + + case PW_TYPE_IPV6_PREFIX: /* IPv6 Prefix and IPv6 */ + return value_data_cidr_cmp_op(op, 16, a->ipv6prefix[1], + (uint8_t const *) &a->ipv6prefix[2], + b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix[2]); + + default: + fr_strerror_printf("Cannot compare IPv6 with IPv4 address"); + return -1; + } + + default: + cmp: + compare = value_data_cmp(a_type, a, a_len, + b_type, b, b_len); + if (compare < -1) { /* comparison error */ + return -1; + } + } + + /* + * Now do the operator comparison. + */ + switch (op) { + case T_OP_CMP_EQ: + return (compare == 0); + + case T_OP_NE: + return (compare != 0); + + case T_OP_LT: + return (compare < 0); + + case T_OP_GT: + return (compare > 0); + + case T_OP_LE: + return (compare <= 0); + + case T_OP_GE: + return (compare >= 0); + + default: + return 0; + } +} + +static char const hextab[] = "0123456789abcdef"; + +/** Convert string value to a value_data_t type + * + * @param[in] ctx to alloc strings in. + * @param[out] dst where to write parsed value. + * @param[in,out] src_type of value data to create/type of value created. + * @param[in] src_enumv DICT_ATTR with string aliases for integer values. + * @param[in] src String to convert. Binary safe for variable length values if len is provided. + * @param[in] src_len may be < 0 in which case strlen(len) is used to determine length, else src_len + * should be the length of the string or sub string to parse. + * @param[in] quote quotation character used to drive de-escaping + * @return length of data written to out or -1 on parse error. + */ +ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst, + PW_TYPE *src_type, DICT_ATTR const *src_enumv, + char const *src, ssize_t src_len, char quote) +{ + DICT_VALUE *dval; + size_t len; + ssize_t ret; + char buffer[256]; + + if (!src) return -1; + + len = (src_len < 0) ? strlen(src) : (size_t)src_len; + + /* + * Set size for all fixed length attributes. + */ + ret = dict_attr_sizes[*src_type][1]; /* Max length */ + + /* + * It's a variable ret src_type so we just alloc a new buffer + * of size len and copy. + */ + switch (*src_type) { + case PW_TYPE_STRING: + { + char *p, *buff; + char const *q; + int x; + + buff = p = talloc_bstrndup(ctx, src, len); + + /* + * No de-quoting. Just copy the string. + */ + if (!quote) { + ret = len; + dst->strvalue = buff; + goto finish; + } + + /* + * Do escaping for single quoted strings. Only + * single quotes get escaped. Everything else is + * left as-is. + */ + if (quote == '\'') { + q = p; + + while (q < (buff + len)) { + /* + * The quotation character is escaped. + */ + if ((q[0] == '\\') && + (q[1] == quote)) { + *(p++) = quote; + q += 2; + continue; + } + + /* + * Two backslashes get mangled to one. + */ + if ((q[0] == '\\') && + (q[1] == '\\')) { + *(p++) = '\\'; + q += 2; + continue; + } + + /* + * Not escaped, just copy it over. + */ + *(p++) = *(q++); + } + + *p = '\0'; + ret = p - buff; + + /* Shrink the buffer to the correct size */ + dst->strvalue = talloc_realloc(ctx, buff, char, ret + 1); + goto finish; + } + + /* + * It's "string" or `string`, do all standard + * escaping. + */ + q = p; + while (q < (buff + len)) { + char c = *q++; + + if ((c == '\\') && (q >= (buff + len))) { + fr_strerror_printf("Invalid escape at end of string"); + talloc_free(buff); + return -1; + } + + /* + * Fix up \X -> ... the binary form of it. + */ + if (c == '\\') { + switch (*q) { + case 'r': + c = '\r'; + q++; + break; + + case 'n': + c = '\n'; + q++; + break; + + case 't': + c = '\t'; + q++; + break; + + case '\\': + c = '\\'; + q++; + break; + + default: + /* + * \" --> ", but only inside of double quoted strings, etc. + */ + if (*q == quote) { + c = quote; + q++; + break; + } + + /* + * \000 --> binary zero character + */ + if ((q[0] >= '0') && + (q[0] <= '9') && + (q[1] >= '0') && + (q[1] <= '9') && + (q[2] >= '0') && + (q[2] <= '9') && + (sscanf(q, "%3o", &x) == 1)) { + c = x; + q += 3; + } + + /* + * Else It's not a recognised escape sequence DON'T + * consume the backslash. This is identical + * behaviour to bash and most other things that + * use backslash escaping. + */ + } + } + + *p++ = c; + } + + *p = '\0'; + ret = p - buff; + dst->strvalue = talloc_realloc(ctx, buff, char, ret + 1); + } + goto finish; + + case PW_TYPE_VSA: + fr_strerror_printf("Must use 'Attr-26 = ...' instead of 'Vendor-Specific = ...'"); + return -1; + + /* raw octets: 0x01020304... */ +#ifndef WITH_ASCEND_BINARY + do_octets: +#endif + case PW_TYPE_OCTETS: + { + uint8_t *p; + + /* + * No 0x prefix, just copy verbatim. + */ + if ((len < 2) || (strncasecmp(src, "0x", 2) != 0)) { + dst->octets = talloc_memdup(ctx, (uint8_t const *)src, len); + talloc_set_type(dst->octets, uint8_t); + ret = len; + goto finish; + } + + len -= 2; + + /* + * Invalid. + */ + if ((len & 0x01) != 0) { + fr_strerror_printf("Length of Hex String is not even, got %zu bytes", len); + return -1; + } + + ret = len >> 1; + p = talloc_array(ctx, uint8_t, ret); + if (fr_hex2bin(p, ret, src + 2, len) != (size_t)ret) { + talloc_free(p); + fr_strerror_printf("Invalid hex data"); + return -1; + } + + dst->octets = p; + } + goto finish; + + case PW_TYPE_ABINARY: +#ifdef WITH_ASCEND_BINARY + if ((len > 1) && (strncasecmp(src, "0x", 2) == 0)) { + ssize_t bin; + + if (len > ((sizeof(dst->filter) + 1) * 2)) { + fr_strerror_printf("Hex data is too large for ascend filter"); + return -1; + } + + bin = fr_hex2bin((uint8_t *) &dst->filter, ret, src + 2, len - 2); + if (bin < ret) { + memset(((uint8_t *) &dst->filter) + bin, 0, ret - bin); + } + } else { + if (ascend_parse_filter(dst, src, len) < 0 ) { + /* Allow ascend_parse_filter's strerror to bubble up */ + return -1; + } + } + + ret = sizeof(dst->filter); + goto finish; +#else + /* + * If Ascend binary is NOT defined, + * then fall through to raw octets, so that + * the user can at least make them by hand... + */ + goto do_octets; +#endif + + /* don't use this! */ + case PW_TYPE_TLV: + fr_strerror_printf("Cannot parse TLV"); + return -1; + + case PW_TYPE_IPV4_ADDR: + { + fr_ipaddr_t addr; + + if (fr_pton4(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1; + + /* + * We allow v4 addresses to have a /32 suffix as some databases (PostgreSQL) + * print them this way. + */ + if (addr.prefix != 32) { + fr_strerror_printf("Invalid IPv4 mask length \"/%i\". Only \"/32\" permitted " + "for non-prefix types", addr.prefix); + return -1; + } + + dst->ipaddr.s_addr = addr.ipaddr.ip4addr.s_addr; + } + goto finish; + + case PW_TYPE_IPV4_PREFIX: + { + fr_ipaddr_t addr; + + if (fr_pton4(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1; + + dst->ipv4prefix[1] = addr.prefix; + memcpy(&dst->ipv4prefix[2], &addr.ipaddr.ip4addr.s_addr, sizeof(dst->ipv4prefix) - 2); + } + goto finish; + + case PW_TYPE_IPV6_ADDR: + { + fr_ipaddr_t addr; + + if (fr_pton6(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1; + + /* + * We allow v6 addresses to have a /128 suffix as some databases (PostgreSQL) + * print them this way. + */ + if (addr.prefix != 128) { + fr_strerror_printf("Invalid IPv6 mask length \"/%i\". Only \"/128\" permitted " + "for non-prefix types", addr.prefix); + return -1; + } + + memcpy(&dst->ipv6addr, addr.ipaddr.ip6addr.s6_addr, sizeof(dst->ipv6addr)); + } + goto finish; + + case PW_TYPE_IPV6_PREFIX: + { + fr_ipaddr_t addr; + + if (fr_pton6(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1; + + dst->ipv6prefix[1] = addr.prefix; + memcpy(&dst->ipv6prefix[2], addr.ipaddr.ip6addr.s6_addr, sizeof(dst->ipv6prefix) - 2); + } + goto finish; + + default: + break; + } + + /* + * It's a fixed size src_type, copy to a temporary buffer and + * \0 terminate if insize >= 0. + */ + if (src_len > 0) { + if (len >= sizeof(buffer)) { + fr_strerror_printf("Temporary buffer too small"); + return -1; + } + + memcpy(buffer, src, src_len); + buffer[src_len] = '\0'; + src = buffer; + } + + switch (*src_type) { + case PW_TYPE_BYTE: + { + char *p; + unsigned int i; + + /* + * Note that ALL integers are unsigned! + */ + i = fr_strtoul(src, &p); + + /* + * Look for the named src for the given + * attribute. + */ + if (src_enumv && *p && !is_whitespace(p)) { + if ((dval = dict_valbyname(src_enumv->attr, src_enumv->vendor, src)) == NULL) { + fr_strerror_printf("Unknown or invalid value \"%s\" for attribute %s", + src, src_enumv->name); + return -1; + } + + dst->byte = dval->value; + } else { + if (i > 255) { + fr_strerror_printf("Byte value \"%s\" is larger than 255", src); + return -1; + } + + dst->byte = i; + } + break; + } + + case PW_TYPE_SHORT: + { + char *p; + unsigned int i; + + /* + * Note that ALL integers are unsigned! + */ + i = fr_strtoul(src, &p); + + /* + * Look for the named src for the given + * attribute. + */ + if (src_enumv && *p && !is_whitespace(p)) { + if ((dval = dict_valbyname(src_enumv->attr, src_enumv->vendor, src)) == NULL) { + fr_strerror_printf("Unknown or invalid value \"%s\" for attribute %s", + src, src_enumv->name); + return -1; + } + + dst->ushort = dval->value; + } else { + if (i > 65535) { + fr_strerror_printf("Short value \"%s\" is larger than 65535", src); + return -1; + } + + dst->ushort = i; + } + break; + } + + case PW_TYPE_INTEGER: + { + char *p; + unsigned int i; + + /* + * Note that ALL integers are unsigned! + */ + i = fr_strtoul(src, &p); + + /* + * Look for the named src for the given + * attribute. + */ + if (src_enumv && *p && !is_whitespace(p)) { + if ((dval = dict_valbyname(src_enumv->attr, src_enumv->vendor, src)) == NULL) { + fr_strerror_printf("Unknown or invalid value \"%s\" for attribute %s", + src, src_enumv->name); + return -1; + } + + dst->integer = dval->value; + } else { + /* + * Value is always within the limits + */ + dst->integer = i; + } + } + break; + + case PW_TYPE_INTEGER64: + { + uint64_t i; + + /* + * Note that ALL integers are unsigned! + */ + if (sscanf(src, "%" PRIu64, &i) != 1) { + fr_strerror_printf("Failed parsing \"%s\" as unsigned 64bit integer", src); + return -1; + } + dst->integer64 = i; + } + break; + + case PW_TYPE_DATE: + { + /* + * time_t may be 64 bits, whule vp_date MUST be 32-bits. We need an + * intermediary variable to handle the conversions. + */ + time_t date; + struct tm tm = { 0 }; + char *end; + + /* + * Try to parse dates via locale-specific names, + * using the same format string as strftime(), + * below. + * + * If that fails (e.g. unix dates as integer), + * then we fall back to our parsing routine, + * which is much more forgiving. + */ + end = strptime(src, "%b %e %Y %H:%M:%S %Z", &tm); + if (end && (*end == '\0')) { + date = mktime(&tm); + + } else if (fr_get_time(src, &date) < 0) { + fr_strerror_printf("failed to parse time string \"%s\"", src); + return -1; + } + + dst->date = date; + } + + break; + + case PW_TYPE_IFID: + if (ifid_aton(src, (void *) dst->ifid) == NULL) { + fr_strerror_printf("Failed to parse interface-id string \"%s\"", src); + return -1; + } + break; + + case PW_TYPE_ETHERNET: + { + char const *c1, *c2, *cp; + size_t p_len = 0; + + /* + * Convert things which are obviously integers to Ethernet addresses + * + * We assume the number is the bigendian representation of the + * ethernet address. + */ + if (is_integer(src)) { + uint64_t integer = htonll(atoll(src)); + + memcpy(dst->ether, &integer, sizeof(dst->ether)); + break; + } + + cp = src; + while (*cp) { + if (cp[1] == ':') { + c1 = hextab; + c2 = memchr(hextab, tolower((uint8_t) cp[0]), 16); + cp += 2; + } else if ((cp[1] != '\0') && ((cp[2] == ':') || (cp[2] == '\0'))) { + c1 = memchr(hextab, tolower((uint8_t) cp[0]), 16); + c2 = memchr(hextab, tolower((uint8_t) cp[1]), 16); + cp += 2; + if (*cp == ':') cp++; + } else { + c1 = c2 = NULL; + } + if (!c1 || !c2 || (p_len >= sizeof(dst->ether))) { + fr_strerror_printf("failed to parse Ethernet address \"%s\"", src); + return -1; + } + dst->ether[p_len] = ((c1-hextab)<<4) + (c2-hextab); + p_len++; + } + } + break; + + /* + * Crazy polymorphic (IPv4/IPv6) attribute src_type for WiMAX. + * + * We try and make is saner by replacing the original + * da, with either an IPv4 or IPv6 da src_type. + * + * These are not dynamic da, and will have the same vendor + * and attribute as the original. + */ + case PW_TYPE_COMBO_IP_ADDR: + { + if (inet_pton(AF_INET6, src, &dst->ipv6addr) > 0) { + *src_type = PW_TYPE_IPV6_ADDR; + ret = dict_attr_sizes[PW_TYPE_COMBO_IP_ADDR][1]; /* size of IPv6 address */ + } else { + fr_ipaddr_t ipaddr; + + if (ip_hton(&ipaddr, AF_INET, src, false) < 0) { + fr_strerror_printf("Failed to find IPv4 address for %s", src); + return -1; + } + + *src_type = PW_TYPE_IPV4_ADDR; + dst->ipaddr.s_addr = ipaddr.ipaddr.ip4addr.s_addr; + ret = dict_attr_sizes[PW_TYPE_COMBO_IP_ADDR][0]; /* size of IPv4 address */ + } + } + break; + + case PW_TYPE_SIGNED: + /* Damned code for 1 WiMAX attribute */ + dst->sinteger = (int32_t)strtol(src, NULL, 10); + break; + + /* + * Anything else. + */ + default: + fr_strerror_printf("Unknown attribute type %d", *src_type); + return -1; + } + +finish: + return ret; +} + +/** Performs byte order reversal for types that need it + * + */ +static ssize_t value_data_hton(value_data_t *dst, PW_TYPE dst_type, void const *src, size_t src_len) +{ + size_t dst_len; + uint8_t *dst_ptr; + + /* 8 byte integers */ + switch (dst_type) { + case PW_TYPE_INTEGER64: + dst_len = sizeof(dst->integer64); + + if (src_len < dst_len) { + too_small: + fr_strerror_printf("Source is too small to cast to destination type"); + return -1; + } + + dst->integer64 = htonll(*(uint64_t const *)src); + break; + + /* 4 byte integers */ + case PW_TYPE_INTEGER: + case PW_TYPE_DATE: + case PW_TYPE_SIGNED: + dst_len = sizeof(dst->integer); + + if (src_len < dst_len) goto too_small; + + dst->integer = htonl(*(uint32_t const *)src); + break; + + /* 2 byte integers */ + case PW_TYPE_SHORT: + dst_len = sizeof(dst->ushort); + + if (src_len < dst_len) goto too_small; + + dst->ushort = htons(*(uint16_t const *)src); + break; + + /* 1 byte integer */ + case PW_TYPE_BYTE: + dst_len = sizeof(dst->byte); + + if (src_len < dst_len) goto too_small; + + dst->byte = *(uint8_t const *)src; + break; + + case PW_TYPE_IPV4_ADDR: + dst_len = 4; + dst_ptr = (uint8_t *) &dst->ipaddr.s_addr; + + copy: + /* + * Not enough information, die. + */ + if (src_len < dst_len) goto too_small; + + /* + * Copy only as much as we need from the source. + */ + memcpy(dst_ptr, src, dst_len); + break; + + case PW_TYPE_ABINARY: + dst_len = sizeof(dst->filter); + dst_ptr = (uint8_t *) dst->filter; + + /* + * Too little data is OK here. + */ + if (src_len < dst_len) { + memcpy(dst_ptr, src, src_len); + memset(dst_ptr + src_len, 0, dst_len - src_len); + break; + } + goto copy; + + case PW_TYPE_IFID: + dst_len = sizeof(dst->ifid); + dst_ptr = (uint8_t *) dst->ifid; + goto copy; + + case PW_TYPE_IPV6_ADDR: + dst_len = sizeof(dst->ipv6addr); + dst_ptr = (uint8_t *) dst->ipv6addr.s6_addr; + goto copy; + + case PW_TYPE_IPV4_PREFIX: + dst_len = sizeof(dst->ipv4prefix); + dst_ptr = (uint8_t *) dst->ipv4prefix; + + if (src_len < dst_len) goto too_small; + if ((((uint8_t const *)src)[1] & 0x3f) > 32) return -1; + goto copy; + + case PW_TYPE_IPV6_PREFIX: + dst_len = sizeof(dst->ipv6prefix); + dst_ptr = (uint8_t *) dst->ipv6prefix; + + /* + * Smaller IPv6 prefixes are OK, too, so long as + * they're not too short. + */ + if (src_len < 2) goto too_small; + + /* + * Prefix is too long. + */ + if (((uint8_t const *)src)[1] > 128) return -1; + + if (src_len < dst_len) { + memcpy(dst_ptr, src, src_len); + memset(dst_ptr + src_len, 0, dst_len - src_len); + break; + } + + goto copy; + + case PW_TYPE_ETHERNET: + dst_len = sizeof(dst->ether); + dst_ptr = (uint8_t *) dst->ether; + goto copy; + + default: + fr_strerror_printf("Invalid cast to %s", + fr_int2str(dict_attr_types, dst_type, "<INVALID>")); + return -1; /* can't do it */ + } + + return dst_len; +} + +/** Convert one type of value_data_t to another + * + * @note This should be the canonical function used to convert between data types. + * + * @param ctx to allocate buffers in (usually the same as dst) + * @param dst Where to write result of casting. + * @param dst_type to cast to. + * @param dst_enumv Enumerated values used to converts strings to integers. + * @param src_type to cast from. + * @param src_enumv Enumerated values used to convert integers to strings. + * @param src Input data. + * @param src_len Input data len. + * @return the length of data in the dst or -1 on error. + */ +ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst, + PW_TYPE dst_type, DICT_ATTR const *dst_enumv, + PW_TYPE src_type, DICT_ATTR const *src_enumv, + value_data_t const *src, size_t src_len) +{ + ssize_t dst_len; + + if (!fr_assert(dst_type != src_type)) { + fr_strerror_printf("Types do not match"); + return -1; + } + + /* + * Deserialise a value_data_t + */ + if (src_type == PW_TYPE_STRING) { + return value_data_from_str(ctx, dst, &dst_type, dst_enumv, src->strvalue, src_len, '\0'); + } + + /* + * Converts the src data to octets with no processing. + */ + if (dst_type == PW_TYPE_OCTETS) { + dst_len = value_data_hton(dst, src_type, src, src_len); + if (dst_len < 0) return -1; + + dst->octets = talloc_memdup(ctx, dst, dst_len); + talloc_set_type(dst->octets, uint8_t); + return dst_len; + } + + /* + * Serialise a value_data_t + */ + if (dst_type == PW_TYPE_STRING) { + dst->strvalue = value_data_aprints(ctx, src_type, src_enumv, src, src_len, '\0'); + return talloc_array_length(dst->strvalue) - 1; + } + + if ((src_type == PW_TYPE_IFID) && + (dst_type == PW_TYPE_INTEGER64)) { + memcpy(&dst->integer64, src->ifid, sizeof(src->ifid)); + dst->integer64 = htonll(dst->integer64); + fixed_length: + return dict_attr_sizes[dst_type][0]; + } + + if ((src_type == PW_TYPE_INTEGER64) && + (dst_type == PW_TYPE_ETHERNET)) { + uint8_t array[8]; + uint64_t i; + + i = htonll(src->integer64); + memcpy(array, &i, 8); + + /* + * For OUIs in the DB. + */ + if ((array[0] != 0) || (array[1] != 0)) return -1; + + memcpy(dst->ether, &array[2], 6); + goto fixed_length; + } + + /* + * For integers, we allow the casting of a SMALL type to + * a larger type, but not vice-versa. + */ + if (dst_type == PW_TYPE_INTEGER64) { + switch (src_type) { + case PW_TYPE_BYTE: + dst->integer64 = src->byte; + break; + + case PW_TYPE_SHORT: + dst->integer64 = src->ushort; + break; + + case PW_TYPE_INTEGER: + dst->integer64 = src->integer; + break; + + case PW_TYPE_DATE: + dst->integer64 = src->date; + break; + + case PW_TYPE_OCTETS: + goto do_octets; + + default: + invalid_cast: + fr_strerror_printf("Invalid cast from %s to %s", + fr_int2str(dict_attr_types, src_type, "<INVALID>"), + fr_int2str(dict_attr_types, dst_type, "<INVALID>")); + return -1; + + } + goto fixed_length; + } + + /* + * We can cast LONG integers to SHORTER ones, so long + * as the long one is on the LHS. + */ + if (dst_type == PW_TYPE_INTEGER) { + switch (src_type) { + case PW_TYPE_BYTE: + dst->integer = src->byte; + break; + + case PW_TYPE_SHORT: + dst->integer = src->ushort; + break; + + case PW_TYPE_DATE: + dst->integer = src->date; + break; + + case PW_TYPE_IPV4_ADDR: + dst->integer = ntohl(src->ipaddr.s_addr); + break; + + case PW_TYPE_OCTETS: + goto do_octets; + + default: + goto invalid_cast; + } + goto fixed_length; + } + + if (dst_type == PW_TYPE_SHORT) { + switch (src_type) { + case PW_TYPE_BYTE: + dst->ushort = src->byte; + break; + + case PW_TYPE_OCTETS: + goto do_octets; + + default: + goto invalid_cast; + } + goto fixed_length; + } + + /* + * We can cast integers less that < INT_MAX to signed + */ + if (dst_type == PW_TYPE_SIGNED) { + switch (src_type) { + case PW_TYPE_BYTE: + dst->sinteger = src->byte; + break; + + case PW_TYPE_SHORT: + dst->sinteger = src->ushort; + break; + + case PW_TYPE_INTEGER: + if (src->integer > INT_MAX) { + fr_strerror_printf("Invalid cast: From integer to signed. integer value %u is larger " + "than max signed int and would overflow", src->integer); + return -1; + } + dst->sinteger = (int)src->integer; + break; + + case PW_TYPE_INTEGER64: + if (src->integer > INT_MAX) { + fr_strerror_printf("Invalid cast: From integer64 to signed. integer64 value %" PRIu64 + " is larger than max signed int and would overflow", src->integer64); + return -1; + } + dst->sinteger = (int)src->integer64; + break; + + case PW_TYPE_OCTETS: + goto do_octets; + + default: + goto invalid_cast; + } + goto fixed_length; + } + /* + * Conversions between IPv4 addresses, IPv6 addresses, IPv4 prefixes and IPv6 prefixes + * + * For prefix to ipaddress conversions, we assume that the host portion has already + * been zeroed out. + * + * We allow casts from v6 to v4 if the v6 address has the correct mapping prefix. + * + * We only allow casts from prefixes to addresses if the prefix is the the length of + * the address, e.g. 32 for ipv4 128 for ipv6. + */ + { + /* + * 10 bytes of 0x00 2 bytes of 0xff + */ + static uint8_t const v4_v6_map[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }; + + switch (dst_type) { + case PW_TYPE_IPV4_ADDR: + switch (src_type) { + case PW_TYPE_IPV6_ADDR: + if (memcmp(src->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map)) != 0) { + bad_v6_prefix_map: + fr_strerror_printf("Invalid cast from %s to %s. No IPv4-IPv6 mapping prefix", + fr_int2str(dict_attr_types, src_type, "<INVALID>"), + fr_int2str(dict_attr_types, dst_type, "<INVALID>")); + return -1; + } + + memcpy(&dst->ipaddr, &src->ipv6addr.s6_addr[sizeof(v4_v6_map)], + sizeof(dst->ipaddr)); + goto fixed_length; + + case PW_TYPE_IPV4_PREFIX: + if (src->ipv4prefix[1] != 32) { + bad_v4_prefix_len: + fr_strerror_printf("Invalid cast from %s to %s. Only /32 prefixes may be " + "cast to IP address types", + fr_int2str(dict_attr_types, src_type, "<INVALID>"), + fr_int2str(dict_attr_types, dst_type, "<INVALID>")); + return -1; + } + + memcpy(&dst->ipaddr, &src->ipv4prefix[2], sizeof(dst->ipaddr)); + goto fixed_length; + + case PW_TYPE_IPV6_PREFIX: + if (src->ipv6prefix[1] != 128) { + bad_v6_prefix_len: + fr_strerror_printf("Invalid cast from %s to %s. Only /128 prefixes may be " + "cast to IP address types", + fr_int2str(dict_attr_types, src_type, "<INVALID>"), + fr_int2str(dict_attr_types, dst_type, "<INVALID>")); + return -1; + } + if (memcmp(&src->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map)) != 0) { + goto bad_v6_prefix_map; + } + memcpy(&dst->ipaddr, &src->ipv6prefix[2 + sizeof(v4_v6_map)], + sizeof(dst->ipaddr)); + goto fixed_length; + + default: + break; + } + break; + + case PW_TYPE_IPV6_ADDR: + switch (src_type) { + case PW_TYPE_IPV4_ADDR: + /* Add the v4/v6 mapping prefix */ + memcpy(dst->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map)); + memcpy(&dst->ipv6addr.s6_addr[sizeof(v4_v6_map)], &src->ipaddr, + sizeof(dst->ipv6addr.s6_addr) - sizeof(v4_v6_map)); + + goto fixed_length; + + case PW_TYPE_IPV4_PREFIX: + if (src->ipv4prefix[1] != 32) goto bad_v4_prefix_len; + + /* Add the v4/v6 mapping prefix */ + memcpy(dst->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map)); + memcpy(&dst->ipv6addr.s6_addr[sizeof(v4_v6_map)], &src->ipv4prefix[2], + sizeof(dst->ipv6addr.s6_addr) - sizeof(v4_v6_map)); + goto fixed_length; + + case PW_TYPE_IPV6_PREFIX: + if (src->ipv4prefix[1] != 128) goto bad_v6_prefix_len; + + memcpy(dst->ipv6addr.s6_addr, &src->ipv6prefix[2], sizeof(dst->ipv6addr.s6_addr)); + goto fixed_length; + + default: + break; + } + break; + + case PW_TYPE_IPV4_PREFIX: + switch (src_type) { + case PW_TYPE_IPV4_ADDR: + memcpy(&dst->ipv4prefix[2], &src->ipaddr, sizeof(dst->ipv4prefix) - 2); + dst->ipv4prefix[0] = 0; + dst->ipv4prefix[1] = 32; + goto fixed_length; + + case PW_TYPE_IPV6_ADDR: + if (memcmp(src->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map)) != 0) { + goto bad_v6_prefix_map; + } + memcpy(&dst->ipv4prefix[2], &src->ipv6addr.s6_addr[sizeof(v4_v6_map)], + sizeof(dst->ipv4prefix) - 2); + dst->ipv4prefix[0] = 0; + dst->ipv4prefix[1] = 32; + goto fixed_length; + + case PW_TYPE_IPV6_PREFIX: + if (memcmp(&src->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map)) != 0) { + goto bad_v6_prefix_map; + } + + /* + * Prefix must be >= 96 bits. If it's < 96 bytes and the + * above check passed, the v6 address wasn't masked + * correctly when it was packet into a value_data_t. + */ + if (!fr_assert(src->ipv6prefix[1] >= (sizeof(v4_v6_map) * 8))) return -1; + + memcpy(&dst->ipv4prefix[2], &src->ipv6prefix[2 + sizeof(v4_v6_map)], + sizeof(dst->ipv4prefix) - 2); + dst->ipv4prefix[0] = 0; + dst->ipv4prefix[1] = src->ipv6prefix[1] - (sizeof(v4_v6_map) * 8); + goto fixed_length; + + default: + break; + } + break; + + case PW_TYPE_IPV6_PREFIX: + switch (src_type) { + case PW_TYPE_IPV4_ADDR: + /* Add the v4/v6 mapping prefix */ + memcpy(&dst->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map)); + memcpy(&dst->ipv6prefix[2 + sizeof(v4_v6_map)], &src->ipaddr, + (sizeof(dst->ipv6prefix) - 2) - sizeof(v4_v6_map)); + dst->ipv6prefix[0] = 0; + dst->ipv6prefix[1] = 128; + goto fixed_length; + + case PW_TYPE_IPV4_PREFIX: + /* Add the v4/v6 mapping prefix */ + memcpy(&dst->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map)); + memcpy(&dst->ipv6prefix[2 + sizeof(v4_v6_map)], &src->ipv4prefix[2], + (sizeof(dst->ipv6prefix) - 2) - sizeof(v4_v6_map)); + dst->ipv6prefix[0] = 0; + dst->ipv6prefix[1] = (sizeof(v4_v6_map) * 8) + src->ipv4prefix[1]; + goto fixed_length; + + case PW_TYPE_IPV6_ADDR: + memcpy(&dst->ipv6prefix[2], &src->ipv6addr, sizeof(dst->ipv6prefix) - 2); + dst->ipv6prefix[0] = 0; + dst->ipv6prefix[1] = 128; + goto fixed_length; + + default: + break; + } + + break; + + default: + break; + } + } + + /* + * The attribute we've found has to have a size which is + * compatible with the type of the destination cast. + */ + if ((src_len < dict_attr_sizes[dst_type][0]) || + (src_len > dict_attr_sizes[dst_type][1])) { + char const *src_type_name; + + src_type_name = fr_int2str(dict_attr_types, src_type, "<INVALID>"); + fr_strerror_printf("Invalid cast from %s to %s. Length should be between %zu and %zu but is %zu", + src_type_name, + fr_int2str(dict_attr_types, dst_type, "<INVALID>"), + dict_attr_sizes[dst_type][0], dict_attr_sizes[dst_type][1], + src_len); + return -1; + } + + if (src_type == PW_TYPE_OCTETS) { + do_octets: + return value_data_hton(dst, dst_type, src->octets, src_len); + } + + /* + * Convert host order to network byte order. + */ + if ((dst_type == PW_TYPE_IPV4_ADDR) && + ((src_type == PW_TYPE_INTEGER) || + (src_type == PW_TYPE_DATE) || + (src_type == PW_TYPE_SIGNED))) { + dst->ipaddr.s_addr = htonl(src->integer); + + } else if ((src_type == PW_TYPE_IPV4_ADDR) && + ((dst_type == PW_TYPE_INTEGER) || + (dst_type == PW_TYPE_DATE) || + (dst_type == PW_TYPE_SIGNED))) { + dst->integer = htonl(src->ipaddr.s_addr); + + } else { /* they're of the same byte order */ + memcpy(&dst, &src, src_len); + } + + return src_len; +} + +/** Copy value data verbatim duplicating any buffers + * + * @param ctx To allocate buffers in. + * @param dst Where to copy value_data to. + * @param src_type Type of src. + * @param src Where to copy value_data from. + * @param src_len Where + */ +ssize_t value_data_copy(TALLOC_CTX *ctx, value_data_t *dst, PW_TYPE src_type, + const value_data_t *src, size_t src_len) +{ + switch (src_type) { + default: + memcpy(dst, src, sizeof(*src)); + break; + + case PW_TYPE_STRING: + dst->strvalue = talloc_bstrndup(ctx, src->strvalue, src_len); + if (!dst->strvalue) return -1; + break; + + case PW_TYPE_OCTETS: + dst->octets = talloc_memdup(ctx, src->octets, src_len); + talloc_set_type(dst->strvalue, uint8_t); + if (!dst->octets) return -1; + break; + } + + return src_len; +} + + + +/** Print one attribute value to a string + * + */ +char *value_data_aprints(TALLOC_CTX *ctx, + PW_TYPE type, DICT_ATTR const *enumv, value_data_t const *data, + size_t inlen, char quote) +{ + char *p = NULL; + unsigned int i; + + switch (type) { + case PW_TYPE_STRING: + { + size_t len, ret; + + if (!quote) { + p = talloc_bstrndup(ctx, data->strvalue, inlen); + if (!p) return NULL; + talloc_set_type(p, char); + return p; + } + + /* Gets us the size of the buffer we need to alloc */ + len = fr_prints_len(data->strvalue, inlen, quote); + p = talloc_array(ctx, char, len); + if (!p) return NULL; + + ret = fr_prints(p, len, data->strvalue, inlen, quote); + if (!fr_assert(ret == (len - 1))) { + talloc_free(p); + return NULL; + } + break; + } + + case PW_TYPE_INTEGER: + i = data->integer; + goto print_int; + + case PW_TYPE_SHORT: + i = data->ushort; + goto print_int; + + case PW_TYPE_BYTE: + i = data->byte; + + print_int: + { + DICT_VALUE const *dv; + + if (enumv && (dv = dict_valbyattr(enumv->attr, enumv->vendor, i))) { + p = talloc_typed_strdup(ctx, dv->name); + } else { + p = talloc_typed_asprintf(ctx, "%u", i); + } + } + break; + + case PW_TYPE_SIGNED: + p = talloc_typed_asprintf(ctx, "%d", data->sinteger); + break; + + case PW_TYPE_INTEGER64: + p = talloc_typed_asprintf(ctx, "%" PRIu64 , data->integer64); + break; + + case PW_TYPE_ETHERNET: + p = talloc_typed_asprintf(ctx, "%02x:%02x:%02x:%02x:%02x:%02x", + data->ether[0], data->ether[1], + data->ether[2], data->ether[3], + data->ether[4], data->ether[5]); + break; + + case PW_TYPE_ABINARY: +#ifdef WITH_ASCEND_BINARY + p = talloc_array(ctx, char, 128); + if (!p) return NULL; + print_abinary(p, 128, (uint8_t const *) &data->filter, inlen, 0); + break; +#else + /* FALL THROUGH */ +#endif + + case PW_TYPE_OCTETS: + p = talloc_array(ctx, char, 2 + 1 + inlen * 2); + if (!p) return NULL; + p[0] = '0'; + p[1] = 'x'; + + fr_bin2hex(p + 2, data->octets, inlen); + p[2 + (inlen * 2)] = '\0'; + break; + + case PW_TYPE_DATE: + { + time_t t; + struct tm s_tm; + + t = data->date; + + p = talloc_zero_array(ctx, char, 64); + strftime(p, 63, "%b %e %Y %H:%M:%S %Z", + localtime_r(&t, &s_tm)); + break; + } + + /* + * We need to use the proper inet_ntop functions for IP + * addresses, else the output might not match output of + * other functions, which makes testing difficult. + * + * An example is tunnelled ipv4 in ipv6 addresses. + */ + case PW_TYPE_IPV4_ADDR: + case PW_TYPE_IPV4_PREFIX: + { + char buff[INET_ADDRSTRLEN + 4]; // + /prefix + + buff[0] = '\0'; + value_data_prints(buff, sizeof(buff), type, enumv, data, inlen, '\0'); + + p = talloc_typed_strdup(ctx, buff); + } + break; + + case PW_TYPE_IPV6_ADDR: + case PW_TYPE_IPV6_PREFIX: + { + char buff[INET6_ADDRSTRLEN + 4]; // + /prefix + + buff[0] = '\0'; + value_data_prints(buff, sizeof(buff), type, enumv, data, inlen, '\0'); + + p = talloc_typed_strdup(ctx, buff); + } + break; + + case PW_TYPE_IFID: + p = talloc_typed_asprintf(ctx, "%x:%x:%x:%x", + (data->ifid[0] << 8) | data->ifid[1], + (data->ifid[2] << 8) | data->ifid[3], + (data->ifid[4] << 8) | data->ifid[5], + (data->ifid[6] << 8) | data->ifid[7]); + break; + + case PW_TYPE_BOOLEAN: + p = talloc_typed_strdup(ctx, data->byte ? "yes" : "no"); + break; + + /* + * Don't add default here + */ + case PW_TYPE_INVALID: + case PW_TYPE_COMBO_IP_ADDR: + case PW_TYPE_COMBO_IP_PREFIX: + case PW_TYPE_TLV: + case PW_TYPE_EXTENDED: + case PW_TYPE_LONG_EXTENDED: + case PW_TYPE_EVS: + case PW_TYPE_VSA: + case PW_TYPE_TIMEVAL: + case PW_TYPE_MAX: + fr_assert(0); + return NULL; + } + + return p; +} + + +/** Print the value of an attribute to a string + * + * @note return value should be checked with is_truncated. + * @note Will always \0 terminate unless outlen == 0. + * + * @param out Where to write the printed version of the attribute value. + * @param outlen Length of the output buffer. + * @param type of data being printed. + * @param enumv Enumerated string values for integer types. + * @param data to print. + * @param inlen Length of data. + * @param quote char to escape in string output. + * @return the number of bytes written to the out buffer, or a number >= outlen if truncation has occurred. + */ +size_t value_data_prints(char *out, size_t outlen, + PW_TYPE type, DICT_ATTR const *enumv, value_data_t const *data, + ssize_t inlen, char quote) +{ + DICT_VALUE *v; + char buf[1024]; /* Interim buffer to use with poorly behaved printing functions */ + char const *a = NULL; + char *p = out; + time_t t; + struct tm s_tm; + unsigned int i; + + size_t len = 0, freespace = outlen; + + if (!data) return 0; + if (outlen == 0) return inlen; + + *out = '\0'; + + p = out; + + switch (type) { + case PW_TYPE_STRING: + + /* + * Ensure that WE add the quotation marks around the string. + */ + if (quote) { + if (freespace < 3) return inlen + 2; + + *p++ = quote; + freespace--; + + len = fr_prints(p, freespace, data->strvalue, inlen, quote); + /* always terminate the quoted string with another quote */ + if (len >= (freespace - 1)) { + /* Use out not p as we're operating on the entire buffer */ + out[outlen - 2] = (char) quote; + out[outlen - 1] = '\0'; + return len + 2; + } + p += len; + freespace -= len; + + *p++ = (char) quote; + freespace--; + *p = '\0'; + + return len + 2; + } + + return fr_prints(out, outlen, data->strvalue, inlen, quote); + + case PW_TYPE_INTEGER: + i = data->integer; + goto print_int; + + case PW_TYPE_SHORT: + i = data->ushort; + goto print_int; + + case PW_TYPE_BYTE: + i = data->byte; + +print_int: + /* Normal, non-tagged attribute */ + if (enumv && (v = dict_valbyattr(enumv->attr, enumv->vendor, i)) != NULL) { + a = v->name; + len = strlen(a); + } else { + /* should never be truncated */ + len = snprintf(buf, sizeof(buf), "%u", i); + a = buf; + } + break; + + case PW_TYPE_INTEGER64: + return snprintf(out, outlen, "%" PRIu64, data->integer64); + + case PW_TYPE_DATE: + t = data->date; + if (quote > 0) { + len = strftime(buf, sizeof(buf) - 1, "%%%b %e %Y %H:%M:%S %Z%%", localtime_r(&t, &s_tm)); + buf[0] = (char) quote; + buf[len - 1] = (char) quote; + buf[len] = '\0'; + } else { + len = strftime(buf, sizeof(buf), "%b %e %Y %H:%M:%S %Z", localtime_r(&t, &s_tm)); + } + a = buf; + break; + + case PW_TYPE_SIGNED: /* Damned code for 1 WiMAX attribute */ + len = snprintf(buf, sizeof(buf), "%d", data->sinteger); + a = buf; + break; + + case PW_TYPE_IPV4_ADDR: + a = inet_ntop(AF_INET, &(data->ipaddr), buf, sizeof(buf)); + len = strlen(buf); + break; + + case PW_TYPE_ABINARY: +#ifdef WITH_ASCEND_BINARY + print_abinary(buf, sizeof(buf), (uint8_t const *) data->filter, inlen, quote); + a = buf; + len = strlen(buf); + break; +#else + /* FALL THROUGH */ +#endif + case PW_TYPE_OCTETS: + case PW_TYPE_TLV: + { + size_t binlen; + size_t hexlen; + + binlen = inlen; + hexlen = (binlen * 2) + 2; /* NOT accounting for trailing NUL */ + + /* + * If the buffer is too small, put something into + * it, and return how much we should have written + * + * 0 + x + H + H + NUL = 5 + */ + if (freespace < 5) { + switch (freespace) { + case '4': + case '3': + out[0] = '0'; + out[1] = 'x'; + out[2] = '\0'; + return hexlen; + + case 2: + *out = '0'; + out++; + /* FALL-THROUGH */ + + case 1: + *out = '\0'; + break; + + case 0: + break; + } + + return hexlen; + } + + /* + * The output buffer is at least 5 bytes, we haev + * room for '0xHH' plus a trailing NUL byte. + */ + out[0] = '0'; + out[1] = 'x'; + + /* + * Get maximum number of bytes we can encode + * given freespace, ensuring we account for '0', + * 'x', and the trailing NUL in the buffer. + * + * Note that we can't have "freespace = 0" after + * this, as 'freespace' has to be at least 5. + */ + freespace -= 3; + freespace /= 2; + if (binlen > freespace) { + binlen = freespace; + } + + fr_bin2hex(out + 2, data->octets, binlen); + return hexlen; + } + + case PW_TYPE_IFID: + a = ifid_ntoa(buf, sizeof(buf), data->ifid); + len = strlen(buf); + break; + + case PW_TYPE_IPV6_ADDR: + a = inet_ntop(AF_INET6, &data->ipv6addr, buf, sizeof(buf)); + len = strlen(buf); + break; + + case PW_TYPE_IPV6_PREFIX: + { + struct in6_addr addr; + + /* + * Alignment issues. + */ + memcpy(&addr, &(data->ipv6prefix[2]), sizeof(addr)); + + a = inet_ntop(AF_INET6, &addr, buf, sizeof(buf)); + if (a) { + p = buf; + + len = strlen(buf); + p += len; + len += snprintf(p, sizeof(buf) - len, "/%u", (unsigned int) data->ipv6prefix[1]); + } + } + break; + + case PW_TYPE_IPV4_PREFIX: + { + struct in_addr addr; + + /* + * Alignment issues. + */ + memcpy(&addr, &(data->ipv4prefix[2]), sizeof(addr)); + + a = inet_ntop(AF_INET, &addr, buf, sizeof(buf)); + if (a) { + p = buf; + + len = strlen(buf); + p += len; + len += snprintf(p, sizeof(buf) - len, "/%u", (unsigned int) (data->ipv4prefix[1] & 0x3f)); + } + } + break; + + case PW_TYPE_ETHERNET: + return snprintf(out, outlen, "%02x:%02x:%02x:%02x:%02x:%02x", + data->ether[0], data->ether[1], + data->ether[2], data->ether[3], + data->ether[4], data->ether[5]); + + /* + * Don't add default here + */ + case PW_TYPE_INVALID: + case PW_TYPE_COMBO_IP_ADDR: + case PW_TYPE_COMBO_IP_PREFIX: + case PW_TYPE_EXTENDED: + case PW_TYPE_LONG_EXTENDED: + case PW_TYPE_EVS: + case PW_TYPE_VSA: + case PW_TYPE_TIMEVAL: + case PW_TYPE_BOOLEAN: + case PW_TYPE_MAX: + fr_assert(0); + *out = '\0'; + return 0; + } + + if (a) strlcpy(out, a, outlen); + + return len; /* Return the number of bytes we would of written (for truncation detection) */ +} + diff --git a/src/lib/version.c b/src/lib/version.c new file mode 100644 index 0000000..c4dc025 --- /dev/null +++ b/src/lib/version.c @@ -0,0 +1,58 @@ +/* + * version.c Validate application and library magic numbers. + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 1999-2014 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +static uint64_t libmagic = RADIUSD_MAGIC_NUMBER; + +/** Check if the application linking to the library has the correct magic number + * + * @param magic number as defined by RADIUSD_MAGIC_NUMBER + * @returns 0 on success, -1 on prefix mismatch, -2 on version mismatch -3 on commit mismatch. + */ +int fr_check_lib_magic(uint64_t magic) +{ + if (MAGIC_PREFIX(magic) != MAGIC_PREFIX(libmagic)) { + fr_strerror_printf("Application and libfreeradius-radius magic number (prefix) mismatch." + " application: %x library: %x", + MAGIC_PREFIX(magic), MAGIC_PREFIX(libmagic)); + return -1; + } + + if (MAGIC_VERSION(magic) != MAGIC_VERSION(libmagic)) { + fr_strerror_printf("Application and libfreeradius-radius magic number (version) mismatch." + " application: %lx library: %lx", + (unsigned long) MAGIC_VERSION(magic), (unsigned long) MAGIC_VERSION(libmagic)); + return -2; + } + + if (MAGIC_COMMIT(magic) != MAGIC_COMMIT(libmagic)) { + fr_strerror_printf("Application and libfreeradius-radius magic number (commit) mismatch." + " application: %lx library: %lx", + (unsigned long) MAGIC_COMMIT(magic), (unsigned long) MAGIC_COMMIT(libmagic)); + return -3; + } + + return 0; +} |