summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:49:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:49:46 +0000
commit50b37d4a27d3295a29afca2286f1a5a086142cec (patch)
tree9212f763934ee090ef72d823f559f52ce387f268 /src/lib
parentInitial commit. (diff)
downloadfreeradius-upstream/3.2.1+dfsg.tar.xz
freeradius-upstream/3.2.1+dfsg.zip
Adding upstream version 3.2.1+dfsg.upstream/3.2.1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/LICENSE504
-rw-r--r--src/lib/README2
-rw-r--r--src/lib/all.mk52
-rw-r--r--src/lib/atomic_queue.c289
-rw-r--r--src/lib/base64.c315
-rw-r--r--src/lib/cbuff.c145
-rw-r--r--src/lib/cursor.c461
-rw-r--r--src/lib/debug.c1215
-rw-r--r--src/lib/dict.c3506
-rw-r--r--src/lib/event.c779
-rw-r--r--src/lib/fifo.c197
-rw-r--r--src/lib/filters.c1253
-rw-r--r--src/lib/getaddrinfo.c438
-rw-r--r--src/lib/hash.c928
-rw-r--r--src/lib/heap.c356
-rw-r--r--src/lib/hmacmd5.c197
-rw-r--r--src/lib/hmacsha1.c228
-rw-r--r--src/lib/isaac.c133
-rw-r--r--src/lib/log.c343
-rw-r--r--src/lib/md4.c310
-rw-r--r--src/lib/md5.c276
-rw-r--r--src/lib/misc.c2188
-rw-r--r--src/lib/missing.c443
-rw-r--r--src/lib/net.c94
-rw-r--r--src/lib/packet.c1042
-rw-r--r--src/lib/pair.c2518
-rw-r--r--src/lib/pcap.c474
-rw-r--r--src/lib/print.c790
-rw-r--r--src/lib/radius.c5153
-rw-r--r--src/lib/rbtree.c744
-rw-r--r--src/lib/regex.c390
-rw-r--r--src/lib/sha1.c185
-rw-r--r--src/lib/snprintf.c880
-rw-r--r--src/lib/snprintf.h220
-rw-r--r--src/lib/socket.c394
-rw-r--r--src/lib/strlcat.c67
-rw-r--r--src/lib/strlcpy.c63
-rw-r--r--src/lib/tcp.c172
-rw-r--r--src/lib/token.c481
-rw-r--r--src/lib/udpfromto.c579
-rw-r--r--src/lib/value.c2013
-rw-r--r--src/lib/version.c58
42 files changed, 30875 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..3580fdf
--- /dev/null
+++ b/src/lib/all.mk
@@ -0,0 +1,52 @@
+#
+# 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
+
+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..605b3a7
--- /dev/null
+++ b/src/lib/atomic_queue.c
@@ -0,0 +1,289 @@
+/*
+ * 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>
+
+/*
+ * Some macros to make our life easier.
+ */
+#define atomic_int64_t _Atomic(int64_t)
+
+typedef struct fr_atomic_queue_entry_t {
+ alignas(128) void *data;
+ atomic_int64_t seq;
+} fr_atomic_queue_entry_t;
+
+struct fr_atomic_queue_t {
+ alignas(128) atomic_int64_t head;
+ atomic_int64_t tail;
+
+ int size;
+
+ fr_atomic_queue_entry_t entry[1];
+};
+
+/** Create fixed-size atomic queue
+ *
+ * @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_create(TALLOC_CTX *ctx, int size)
+{
+ int i;
+ int64_t seq;
+ fr_atomic_queue_t *aq;
+
+ 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.
+ */
+ aq = talloc_size(ctx, sizeof(*aq) + (size - 1) * sizeof(aq->entry[0]));
+ if (!aq) return NULL;
+
+ talloc_set_name(aq, "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;
+}
+
+
+/** 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;
+}
+
+#ifndef NDEBUG
+
+#if 0
+typedef struct fr_control_message_t {
+ 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)
+{
+ int i;
+ int64_t head, tail;
+
+ head = load(aq->head);
+ tail = load(aq->head);
+
+ fprintf(fp, "AQ %p size %d, 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[%d] = { %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..be702cf
--- /dev/null
+++ b/src/lib/debug.c
@@ -0,0 +1,1215 @@
+/*
+ * 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.
+ */
+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;
+}
+
+/** 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..479bf11
--- /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)
+{
+ 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((int) *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((int) p[0]) ||
+ (p[1] != ',') ||
+ !isdigit((int) 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((int) 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..0c2976b
--- /dev/null
+++ b/src/lib/event.c
@@ -0,0 +1,779 @@
+/*
+ * 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>
+
+#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;
+ 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;
+
+ bool changed;
+
+#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->changed = true; /* force re-set of fds's */
+
+#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;
+ break;
+ }
+ }
+#endif
+
+ if (!ef) {
+ fr_strerror_printf("Failed assigning FD");
+ return 0;
+ }
+
+ ef->fd = fd;
+ ef->handler = handler;
+ ef->ctx = ctx;
+
+#ifndef HAVE_KQUEUE
+ el->changed = true;
+#endif
+
+ return 1;
+}
+
+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);
+
+ 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;
+ el->changed = true;
+ 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
+ int maxfd = 0;
+ fd_set read_fds, master_fds;
+
+ el->changed = true;
+#endif
+
+ el->exit = 0;
+ el->dispatch = true;
+
+ while (!el->exit) {
+#ifndef HAVE_KQUEUE
+ /*
+ * Cache the list of FD's to watch.
+ */
+ if (el->changed) {
+#ifdef __clang_analyzer__
+ memset(&master_fds, 0, sizeof(master_fds));
+#else
+ FD_ZERO(&master_fds);
+#endif
+ for (i = 0; i < el->max_readers; i++) {
+ if (el->readers[i].fd < 0) continue;
+
+ if (el->readers[i].fd > maxfd) {
+ maxfd = el->readers[i].fd;
+ }
+ FD_SET(el->readers[i].fd, &master_fds);
+ }
+
+ el->changed = false;
+ }
+#endif /* HAVE_KQUEUE */
+
+ /*
+ * 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 = master_fds;
+ rcode = select(maxfd + 1, &read_fds, NULL, 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;
+
+ if (!FD_ISSET(ef->fd, &read_fds)) continue;
+
+ ef->handler(el, ef->fd, ef->ctx);
+
+ if (el->changed) break;
+ }
+
+#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;
+ }
+
+ /*
+ * 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++];
+ 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..2022362
--- /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((int)*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..f9d0881
--- /dev/null
+++ b/src/lib/hash.c
@@ -0,0 +1,928 @@
+/*
+ * 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)
+{
+ return ((reversed_byte[key & 0xff] << 24) |
+ (reversed_byte[(key >> 8) & 0xff] << 16) |
+ (reversed_byte[(key >> 16) & 0xff] << 8) |
+ (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..9dbb73a
--- /dev/null
+++ b/src/lib/misc.c
@@ -0,0 +1,2188 @@
+/*
+ * 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(*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 ret[2], *o = ret;
+
+ if (prefix > 128) prefix = 128;
+
+ /* Short circuit */
+ if (prefix == 128) return *ipaddr;
+
+ if (prefix >= 64) {
+ prefix -= 64;
+ *o++ = 0xffffffffffffffffULL & *p++; /* lhs portion masked */
+ } else {
+ ret[1] = 0; /* rhs portion zeroed */
+ }
+
+ /* Max left shift is 63 else we get overflow */
+ if (prefix > 0) {
+ *o = htonll(~((uint64_t)(0x0000000000000001ULL << (64 - prefix)) - 1)) & *p;
+ } 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((int) hex[i << 1]), sizeof(hextab))) ||
+ !(c2 = memchr(hextab, tolower((int) 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(*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(*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(*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 *) &num;
+ 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..acba8d9
--- /dev/null
+++ b/src/lib/packet.c
@@ -0,0 +1,1042 @@
+/*
+ * 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;
+
+ /*
+ * 256-way fanout.
+ */
+ if (a->id < b->id) return -1;
+ if (a->id > b->id) return +1;
+
+ if (a->sockfd < b->sockfd) return -1;
+ if (a->sockfd > b->sockfd) return +1;
+
+ /*
+ * 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;
+#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
+
+ 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,
+ 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;
+#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
+ 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;
+
+ /*
+ * All IDs are allocated: ignore it.
+ */
+ if (ps->num_outgoing == 256) 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;
+
+ /*
+ * 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;
+
+ 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[(request->id >> 3) & 0x1f] &= ~(1 << (request->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;
+
+#if 0
+ if (!ps->id[(request->id >> 3) & 0x1f] & (1 << (request->id & 0x07))) {
+ fr_exit(1);
+ }
+#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:%i to %s%s%s:%i 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:%i 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..898e1e8
--- /dev/null
+++ b/src/lib/pair.c
@@ -0,0 +1,2518 @@
+/*
+ * 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((int) 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((int) p[1]))) {
+ if (q >= (raw->l_opand + sizeof(raw->l_opand))) {
+ goto too_long;
+ }
+ *(q++) = *(p++);
+
+ while (isdigit((int) *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);
+
+ 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..524e680
--- /dev/null
+++ b/src/lib/radius.c
@@ -0,0 +1,5153 @@
+/*
+ * 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;
+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 */
+
+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;
+}
+
+
+#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);
+}
+
+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;
+
+ /*
+ * Encrypt the various password styles
+ *
+ * Attributes with encrypted values MUST be less than
+ * 128 bytes long.
+ */
+ switch (vp->da->flags.encrypt) {
+ 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;
+
+
+ default:
+ 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) {
+ 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;
+ 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;
+ }
+
+ /*
+ * 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)) {
+ 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;
+}
+
+
+/** Sign a previously encoded packet
+ *
+ */
+int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
+ char const *secret)
+{
+ radius_packet_t *hdr = (radius_packet_t *)packet->data;
+
+ /*
+ * 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;
+ }
+
+ 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;
+ }
+
+ /*
+ * 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
+
+ /*
+ * 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);
+ }
+
+ /*
+ * Copy the request authenticator over to the packet.
+ */
+ memcpy(hdr->vector, packet->vector, AUTH_VECTOR_LEN);
+
+ /*
+ * 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 */
+
+ 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 */
+}
+
+
+/** 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;
+}
+
+/** 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];
+ bool require_ma = false;
+ bool seen_ma = false;
+ uint32_t num_attributes;
+ decode_fail_t failure = DECODE_FAIL_NONE;
+ bool eap = false;
+ bool non_eap = false;
+
+ /*
+ * 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;
+ }
+
+ /*
+ * 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:
+ 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;
+ }
+
+ /*
+ * 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 && !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;
+ }
+
+ 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;
+ }
+
+ /*
+ * Fill RADIUS header fields
+ */
+ packet->code = hdr->code;
+ packet->id = hdr->id;
+ 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, RADIUS_PACKET *original, char const *secret)
+{
+ uint8_t *ptr;
+ int length;
+ int attrlen;
+ int rcode;
+ char buffer[32];
+
+ if (!packet || !packet->data) return -1;
+
+ /*
+ * 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) {
+ uint8_t msg_auth_vector[AUTH_VECTOR_LEN];
+ uint8_t calc_auth_vector[AUTH_VECTOR_LEN];
+
+ attrlen = ptr[1];
+
+ switch (ptr[0]) {
+ default: /* don't do anything. */
+ break;
+
+ /*
+ * Note that more than one Message-Authenticator
+ * attribute is invalid.
+ */
+ case PW_MESSAGE_AUTHENTICATOR:
+ 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 */
+
+ 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;
+ }
+
+ /*
+ * 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;
+ }
+
+ 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) ||
+ (da->flags.encrypt == FLAG_ENCRYPT_TUNNEL_PASSWORD))) {
+ /*
+ * 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;
+ }
+
+ /*
+ * Decrypt the attribute.
+ */
+ if (secret && packet && (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 */
+ }
+
+ /*
+ * 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;
+
+ /*
+ * 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;
+}
+
+
+/** 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;
+}
+
+
+/** 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] ^= 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++];
+ 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;
+#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;
+}
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..4f79a32
--- /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(*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(*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..3c88b78
--- /dev/null
+++ b/src/lib/socket.c
@@ -0,0 +1,394 @@
+/*
+ * 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>
+
+#ifdef HAVE_SYS_UN_H
+# include <sys/un.h>
+# ifndef SUN_LEN
+# define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+# endif
+
+/** 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/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..a797862
--- /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((int) *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((int) *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((int) *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((int)*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..05d42e2
--- /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((int) cp[0]), 16);
+ cp += 2;
+ } else if ((cp[1] != '\0') && ((cp[2] == ':') || (cp[2] == '\0'))) {
+ c1 = memchr(hextab, tolower((int) cp[0]), 16);
+ c2 = memchr(hextab, tolower((int) 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;
+}