From deb8fd80395b72a6dd3f339c6e5847eebf855196 Mon Sep 17 00:00:00 2001 From: Thorsten Alteholz Date: Wed, 13 Dec 2017 19:05:39 +0100 Subject: Import osmo-hlr_0.1.0.orig.tar.xz [dgit import orig osmo-hlr_0.1.0.orig.tar.xz] --- .gitignore | 31 + .gitreview | 3 + COPYING | 661 +++++++++ Makefile.am | 19 + configure.ac | 74 + contrib/ipa.py | 278 ++++ contrib/jenkins.sh | 46 + contrib/systemd/osmo-hlr.service | 11 + doc/examples/osmo-hlr.cfg | 19 + git-version-gen | 151 +++ sql/Makefile.am | 7 + sql/hlr.sql | 69 + sql/hlr_data.sql | 13 + src/Makefile.am | 109 ++ src/auc.c | 184 +++ src/auc.h | 8 + src/ctrl.c | 398 ++++++ src/ctrl.h | 34 + src/db.c | 280 ++++ src/db.h | 142 ++ src/db_auc.c | 225 ++++ src/db_bootstrap.sed | 25 + src/db_hlr.c | 717 ++++++++++ src/db_test.c | 87 ++ src/dbd_decode_binary.c | 42 + src/gsup_router.c | 85 ++ src/gsup_router.h | 8 + src/gsup_server.c | 335 +++++ src/gsup_server.h | 50 + src/hlr.c | 500 +++++++ src/hlr.h | 40 + src/hlr_db_tool.c | 426 ++++++ src/hlr_vty.c | 141 ++ src/hlr_vty.h | 37 + src/hlr_vty_subscr.c | 484 +++++++ src/hlr_vty_subscr.h | 3 + src/logging.c | 27 + src/logging.h | 12 + src/luop.c | 290 ++++ src/luop.h | 83 ++ src/populate_hlr_db.pl | 75 ++ src/rand.h | 7 + src/rand_fake.c | 52 + src/rand_urandom.c | 38 + tests/Makefile.am | 96 ++ tests/auc/Makefile.am | 57 + tests/auc/auc_test.c | 627 +++++++++ tests/auc/auc_test.err | 353 +++++ tests/auc/auc_test.ok | 2 + tests/auc/auc_ts_55_205_test_sets.err | 418 ++++++ tests/auc/auc_ts_55_205_test_sets.ok | 2 + tests/auc/gen_ts_55_205_test_sets/Makefile.am | 6 + tests/auc/gen_ts_55_205_test_sets/func_template.c | 66 + tests/auc/gen_ts_55_205_test_sets/main_template.c | 116 ++ tests/auc/gen_ts_55_205_test_sets/pdftxt_2_c.py | 99 ++ .../gen_ts_55_205_test_sets/ts55_205_test_sets.txt | 972 ++++++++++++++ tests/db/Makefile.am | 48 + tests/db/db_test.c | 846 ++++++++++++ tests/db/db_test.err | 1410 ++++++++++++++++++++ tests/db/db_test.ok | 2 + tests/gsup_server/Makefile.am | 40 + tests/gsup_server/gsup_server_test.c | 145 ++ tests/gsup_server/gsup_server_test.err | 0 tests/gsup_server/gsup_server_test.ok | 94 ++ tests/test_nodes.vty | 117 ++ tests/test_subscriber.ctrl | 614 +++++++++ tests/test_subscriber.sql | 17 + tests/test_subscriber.vty | 349 +++++ tests/test_subscriber_errors.ctrl | 107 ++ tests/testsuite.at | 31 + 70 files changed, 12960 insertions(+) create mode 100644 .gitignore create mode 100644 .gitreview create mode 100644 COPYING create mode 100644 Makefile.am create mode 100644 configure.ac create mode 100755 contrib/ipa.py create mode 100755 contrib/jenkins.sh create mode 100644 contrib/systemd/osmo-hlr.service create mode 100644 doc/examples/osmo-hlr.cfg create mode 100755 git-version-gen create mode 100644 sql/Makefile.am create mode 100644 sql/hlr.sql create mode 100644 sql/hlr_data.sql create mode 100644 src/Makefile.am create mode 100644 src/auc.c create mode 100644 src/auc.h create mode 100644 src/ctrl.c create mode 100644 src/ctrl.h create mode 100644 src/db.c create mode 100644 src/db.h create mode 100644 src/db_auc.c create mode 100644 src/db_bootstrap.sed create mode 100644 src/db_hlr.c create mode 100644 src/db_test.c create mode 100644 src/dbd_decode_binary.c create mode 100644 src/gsup_router.c create mode 100644 src/gsup_router.h create mode 100644 src/gsup_server.c create mode 100644 src/gsup_server.h create mode 100644 src/hlr.c create mode 100644 src/hlr.h create mode 100644 src/hlr_db_tool.c create mode 100644 src/hlr_vty.c create mode 100644 src/hlr_vty.h create mode 100644 src/hlr_vty_subscr.c create mode 100644 src/hlr_vty_subscr.h create mode 100644 src/logging.c create mode 100644 src/logging.h create mode 100644 src/luop.c create mode 100644 src/luop.h create mode 100755 src/populate_hlr_db.pl create mode 100644 src/rand.h create mode 100644 src/rand_fake.c create mode 100644 src/rand_urandom.c create mode 100644 tests/Makefile.am create mode 100644 tests/auc/Makefile.am create mode 100644 tests/auc/auc_test.c create mode 100644 tests/auc/auc_test.err create mode 100644 tests/auc/auc_test.ok create mode 100644 tests/auc/auc_ts_55_205_test_sets.err create mode 100644 tests/auc/auc_ts_55_205_test_sets.ok create mode 100644 tests/auc/gen_ts_55_205_test_sets/Makefile.am create mode 100644 tests/auc/gen_ts_55_205_test_sets/func_template.c create mode 100644 tests/auc/gen_ts_55_205_test_sets/main_template.c create mode 100755 tests/auc/gen_ts_55_205_test_sets/pdftxt_2_c.py create mode 100644 tests/auc/gen_ts_55_205_test_sets/ts55_205_test_sets.txt create mode 100644 tests/db/Makefile.am create mode 100644 tests/db/db_test.c create mode 100644 tests/db/db_test.err create mode 100644 tests/db/db_test.ok create mode 100644 tests/gsup_server/Makefile.am create mode 100644 tests/gsup_server/gsup_server_test.c create mode 100644 tests/gsup_server/gsup_server_test.err create mode 100644 tests/gsup_server/gsup_server_test.ok create mode 100644 tests/test_nodes.vty create mode 100644 tests/test_subscriber.ctrl create mode 100644 tests/test_subscriber.sql create mode 100644 tests/test_subscriber.vty create mode 100644 tests/test_subscriber_errors.ctrl create mode 100644 tests/testsuite.at diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72f11a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +*.o +*.db +*.pyc +.*.sw? +.version +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +compile +config.guess +config.log +config.status +config.sub +configure +depcomp +install-sh +libtool +ltmain.sh +m4 +*.m4 +missing +.deps + +src/osmo-hlr +src/db_test + +tests/testsuite +tests/auc/auc_3g_test +tests/auc/auc_ts_55_205_test_sets.c +tests/auc/auc_ts_55_205_test_sets diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..afddc7b --- /dev/null +++ b/.gitreview @@ -0,0 +1,3 @@ +[gerrit] +host=gerrit.osmocom.org +project=osmo-hlr diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..392d80d --- /dev/null +++ b/Makefile.am @@ -0,0 +1,19 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 + +SUBDIRS = \ + src \ + sql \ + tests \ + $(NULL) + +EXTRA_DIST = \ + .version \ + $(NULL) + +@RELMAKE@ + +BUILT_SOURCES = $(top_srcdir)/.version +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..1db32d4 --- /dev/null +++ b/configure.ac @@ -0,0 +1,74 @@ +AC_INIT([osmo-hlr], + m4_esyscmd([./git-version-gen .tarball-version]), + [openbsc@lists.osmocom.org]) + +dnl *This* is the root dir, even if an install-sh exists in ../ or ../../ +AC_CONFIG_AUX_DIR([.]) + +dnl libtool init +LT_INIT + +AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip 1.9]) + +AC_CONFIG_TESTDIR(tests) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl include release helper +RELMAKE='-include osmo-release.mk' +AC_SUBST([RELMAKE]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_MKDIR_P +AC_PROG_CC +AC_PROG_INSTALL + +dnl check for pkg-config (explained in detail in libosmocore/configure.ac) +AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no) +if test "x$PKG_CONFIG_INSTALLED" = "xno"; then + AC_MSG_WARN([You need to install pkg-config]) +fi +PKG_PROG_PKG_CONFIG([0.20]) + +PKG_CHECK_MODULES(TALLOC, [talloc >= 2.0.1]) + +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.9.5) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.9.0) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.9.0) +PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl) +PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.3.2) + +PKG_CHECK_MODULES(SQLITE3, sqlite3) + +AC_CONFIG_MACRO_DIR([m4]) + +dnl checks for header files +AC_HEADER_STDC + +AC_ARG_ENABLE([external_tests], + AC_HELP_STRING([--enable-external-tests], + [Include the VTY/CTRL tests in make check [default=no]]), + [enable_ext_tests="$enableval"],[enable_ext_tests="no"]) +if test "x$enable_ext_tests" = "xyes" ; then + AM_PATH_PYTHON + AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmotestvty.py,yes) + if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then + AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.]) + fi +fi +AC_MSG_CHECKING([whether to enable VTY/CTRL tests]) +AC_MSG_RESULT([$enable_ext_tests]) +AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes") + +AC_OUTPUT( + Makefile + src/Makefile + sql/Makefile + tests/Makefile + tests/auc/Makefile + tests/auc/gen_ts_55_205_test_sets/Makefile + tests/gsup_server/Makefile + tests/db/Makefile + ) diff --git a/contrib/ipa.py b/contrib/ipa.py new file mode 100755 index 0000000..71cbf45 --- /dev/null +++ b/contrib/ipa.py @@ -0,0 +1,278 @@ +#!/usr/bin/python3 +# -*- mode: python-mode; py-indent-tabs-mode: nil -*- +""" +/* + * Copyright (C) 2016 sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +""" + +import struct, random, sys + +class IPA(object): + """ + Stateless IPA protocol multiplexer: add/remove/parse (extended) header + """ + version = "0.0.5" + TCP_PORT_OML = 3002 + TCP_PORT_RSL = 3003 + # OpenBSC extensions: OSMO, MGCP_OLD + PROTO = dict(RSL=0x00, CCM=0xFE, SCCP=0xFD, OML=0xFF, OSMO=0xEE, MGCP_OLD=0xFC) + # ...OML Router Control, GSUP GPRS extension, Osmocom Authn Protocol + EXT = dict(CTRL=0, MGCP=1, LAC=2, SMSC=3, ORC=4, GSUP=5, OAP=6) + # OpenBSC extension: SCCP_OLD + MSGT = dict(PING=0x00, PONG=0x01, ID_GET=0x04, ID_RESP=0x05, ID_ACK=0x06, SCCP_OLD=0xFF) + _IDTAG = dict(SERNR=0, UNITNAME=1, LOCATION=2, TYPE=3, EQUIPVERS=4, SWVERSION=5, IPADDR=6, MACADDR=7, UNIT=8) + CTRL_GET = 'GET' + CTRL_SET = 'SET' + CTRL_REP = 'REPLY' + CTRL_ERR = 'ERR' + CTRL_TRAP = 'TRAP' + + def _l(self, d, p): + """ + Reverse dictionary lookup: return key for a given value + """ + if p is None: + return 'UNKNOWN' + return list(d.keys())[list(d.values()).index(p)] + + def _tag(self, t, v): + """ + Create TAG as TLV data + """ + return struct.pack(">HB", len(v) + 1, t) + v + + def proto(self, p): + """ + Lookup protocol name + """ + return self._l(self.PROTO, p) + + def ext(self, p): + """ + Lookup protocol extension name + """ + return self._l(self.EXT, p) + + def msgt(self, p): + """ + Lookup message type name + """ + return self._l(self.MSGT, p) + + def idtag(self, p): + """ + Lookup ID tag name + """ + return self._l(self._IDTAG, p) + + def ext_name(self, proto, exten): + """ + Return proper extension byte name depending on the protocol used + """ + if self.PROTO['CCM'] == proto: + return self.msgt(exten) + if self.PROTO['OSMO'] == proto: + return self.ext(exten) + return None + + def add_header(self, data, proto, ext=None): + """ + Add IPA header (with extension if necessary), data must be represented as bytes + """ + if ext is None: + return struct.pack(">HB", len(data) + 1, proto) + data + return struct.pack(">HBB", len(data) + 1, proto, ext) + data + + def del_header(self, data): + """ + Strip IPA protocol header correctly removing extension if present + Returns data length, IPA protocol, extension (or None if not defined for a give protocol) and the data without header + """ + if not len(data): + return None, None, None, None + (dlen, proto) = struct.unpack('>HB', data[:3]) + if self.PROTO['OSMO'] == proto or self.PROTO['CCM'] == proto: # there's extension which we have to unpack + return struct.unpack('>HBB', data[:4]) + (data[4:], ) # length, protocol, extension, data + return dlen, proto, None, data[3:] # length, protocol, _, data + + def split_combined(self, data): + """ + Split the data which contains multiple concatenated IPA messages into tuple (first, rest) where rest contains remaining messages, first is the single IPA message + """ + (length, _, _, _) = self.del_header(data) + return data[:(length + 3)], data[(length + 3):] + + def tag_serial(self, data): + """ + Make TAG for serial number + """ + return self._tag(self._IDTAG['SERNR'], data) + + def tag_name(self, data): + """ + Make TAG for unit name + """ + return self._tag(self._IDTAG['UNITNAME'], data) + + def tag_loc(self, data): + """ + Make TAG for location + """ + return self._tag(self._IDTAG['LOCATION'], data) + + def tag_type(self, data): + """ + Make TAG for unit type + """ + return self._tag(self._IDTAG['TYPE'], data) + + def tag_equip(self, data): + """ + Make TAG for equipment version + """ + return self._tag(self._IDTAG['EQUIPVERS'], data) + + def tag_sw(self, data): + """ + Make TAG for software version + """ + return self._tag(self._IDTAG['SWVERSION'], data) + + def tag_ip(self, data): + """ + Make TAG for IP address + """ + return self._tag(self._IDTAG['IPADDR'], data) + + def tag_mac(self, data): + """ + Make TAG for MAC address + """ + return self._tag(self._IDTAG['MACADDR'], data) + + def tag_unit(self, data): + """ + Make TAG for unit ID + """ + return self._tag(self._IDTAG['UNIT'], data) + + def identity(self, unit=b'', mac=b'', location=b'', utype=b'', equip=b'', sw=b'', name=b'', serial=b''): + """ + Make IPA IDENTITY tag list, by default returns empty concatenated bytes of tag list + """ + return self.tag_unit(unit) + self.tag_mac(mac) + self.tag_loc(location) + self.tag_type(utype) + self.tag_equip(equip) + self.tag_sw(sw) + self.tag_name(name) + self.tag_serial(serial) + + def ping(self): + """ + Make PING message + """ + return self.add_header(b'', self.PROTO['CCM'], self.MSGT['PING']) + + def pong(self): + """ + Make PONG message + """ + return self.add_header(b'', self.PROTO['CCM'], self.MSGT['PONG']) + + def id_ack(self): + """ + Make ID_ACK CCM message + """ + return self.add_header(b'', self.PROTO['CCM'], self.MSGT['ID_ACK']) + + def id_get(self): + """ + Make ID_GET CCM message + """ + return self.add_header(self.identity(), self.PROTO['CCM'], self.MSGT['ID_GET']) + + def id_resp(self, data): + """ + Make ID_RESP CCM message + """ + return self.add_header(data, self.PROTO['CCM'], self.MSGT['ID_RESP']) + +class Ctrl(IPA): + """ + Osmocom CTRL protocol implemented on top of IPA multiplexer + """ + def __init__(self): + random.seed() + + def add_header(self, data): + """ + Add CTRL header + """ + return super(Ctrl, self).add_header(data.encode('utf-8'), IPA.PROTO['OSMO'], IPA.EXT['CTRL']) + + def rem_header(self, data): + """ + Remove CTRL header, check for appropriate protocol and extension + """ + (_, proto, ext, d) = super(Ctrl, self).del_header(data) + if self.PROTO['OSMO'] != proto or self.EXT['CTRL'] != ext: + return None + return d + + def parse(self, data, op=None): + """ + Parse Ctrl string returning (var, value) pair + var could be None in case of ERROR message + value could be None in case of GET message + """ + (s, i, v) = data.split(' ', 2) + if s == self.CTRL_ERR: + return None, v + if s == self.CTRL_GET: + return v, None + (s, i, var, val) = data.split(' ', 3) + if s == self.CTRL_TRAP and i != '0': + return None, '%s with non-zero id %s' % (s, i) + if op is not None and i != op: + if s == self.CTRL_GET + '_' + self.CTRL_REP or s == self.CTRL_SET + '_' + self.CTRL_REP: + return None, '%s with unexpected id %s' % (s, i) + return var, val + + def trap(self, var, val): + """ + Make TRAP message with given (vak, val) pair + """ + return self.add_header("%s 0 %s %s" % (self.CTRL_TRAP, var, val)) + + def cmd(self, var, val=None): + """ + Make SET/GET command message: returns (r, m) tuple where r is random operation id and m is assembled message + """ + r = random.randint(1, sys.maxsize) + if val is not None: + return r, self.add_header("%s %s %s %s" % (self.CTRL_SET, r, var, val)) + return r, self.add_header("%s %s %s" % (self.CTRL_GET, r, var)) + + def verify(self, reply, r, var, val=None): + """ + Verify reply to SET/GET command: returns (b, v) tuple where v is True/False verification result and v is the variable value + """ + (k, v) = self.parse(reply) + if k != var or (val is not None and v != val): + return False, v + return True, v + +if __name__ == '__main__': + print("IPA multiplexer v%s loaded." % IPA.version) diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh new file mode 100755 index 0000000..d159db9 --- /dev/null +++ b/contrib/jenkins.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# jenkins build helper script for osmo-hlr. This is how we build on jenkins.osmocom.org + +if ! [ -x "$(command -v osmo-build-dep.sh)" ]; then + echo "Error: We need to have scripts/osmo-deps.sh from http://git.osmocom.org/osmo-ci/ in PATH !" + exit 2 +fi + + +set -ex + +base="$PWD" +deps="$base/deps" +inst="$deps/install" +export deps inst + +osmo-clean-workspace.sh + +mkdir "$deps" || true + +verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]") + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmocore "" ac_cv_path_DOXYGEN=false +osmo-build-dep.sh libosmo-abis + +set +x +echo +echo +echo +echo " =============================== osmo-hlr ===============================" +echo +set -x + +cd "$base" +autoreconf --install --force +./configure --enable-external-tests +$MAKE $PARALLEL_MAKE +if [ "x$label" != "xFreeBSD_amd64" ]; then + $MAKE check || cat-testlogs.sh + $MAKE distcheck || cat-testlogs.sh +fi + +osmo-clean-workspace.sh diff --git a/contrib/systemd/osmo-hlr.service b/contrib/systemd/osmo-hlr.service new file mode 100644 index 0000000..64e369d --- /dev/null +++ b/contrib/systemd/osmo-hlr.service @@ -0,0 +1,11 @@ +[Unit] +Description=Osmocom Home Location Register (OsmoHLR) + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/osmo-hlr -c /etc/osmocom/osmo-hlr.cfg -l /var/lib/osmocom/hlr.db +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/doc/examples/osmo-hlr.cfg b/doc/examples/osmo-hlr.cfg new file mode 100644 index 0000000..853d3c2 --- /dev/null +++ b/doc/examples/osmo-hlr.cfg @@ -0,0 +1,19 @@ +! +! OsmoHLR example configuration +! +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 1 + logging print extended-timestamp 1 + logging level all debug + logging level linp error +! +line vty + bind 127.0.0.1 +ctrl + bind 127.0.0.1 +hlr + gsup + bind ip 127.0.0.1 diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 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, see . + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/sql/Makefile.am b/sql/Makefile.am new file mode 100644 index 0000000..ab44e2a --- /dev/null +++ b/sql/Makefile.am @@ -0,0 +1,7 @@ +EXTRA_DIST = \ + hlr_data.sql \ + hlr.sql \ + $(NULL) + +docsdir = $(datadir)/doc/osmo-hlr +docs_DATA = $(srcdir)/hlr.sql diff --git a/sql/hlr.sql b/sql/hlr.sql new file mode 100644 index 0000000..80eb3e5 --- /dev/null +++ b/sql/hlr.sql @@ -0,0 +1,69 @@ +CREATE TABLE IF NOT EXISTS subscriber ( +-- OsmoHLR's DB scheme is modelled roughly after TS 23.008 version 13.3.0 + id INTEGER PRIMARY KEY, + -- Chapter 2.1.1.1 + imsi VARCHAR(15) UNIQUE NOT NULL, + -- Chapter 2.1.2 + msisdn VARCHAR(15) UNIQUE, + -- Chapter 2.2.3: Most recent / current IMEI + imeisv VARCHAR, + -- Chapter 2.4.5 + vlr_number VARCHAR(15), + -- Chapter 2.4.6 + hlr_number VARCHAR(15), + -- Chapter 2.4.8.1 + sgsn_number VARCHAR(15), + -- Chapter 2.13.10 + sgsn_address VARCHAR, + -- Chapter 2.4.8.2 + ggsn_number VARCHAR(15), + -- Chapter 2.4.9.2 + gmlc_number VARCHAR(15), + -- Chapter 2.4.23 + smsc_number VARCHAR(15), + -- Chapter 2.4.24 + periodic_lu_tmr INTEGER, + -- Chapter 2.13.115 + periodic_rau_tau_tmr INTEGER, + -- Chapter 2.1.1.2: network access mode + nam_cs BOOLEAN NOT NULL DEFAULT 1, + nam_ps BOOLEAN NOT NULL DEFAULT 1, + -- Chapter 2.1.8 + lmsi INTEGER, + + -- The below purged flags might not even be stored non-volatile, + -- refer to TS 23.012 Chapter 3.6.1.4 + -- Chapter 2.7.5 + ms_purged_cs BOOLEAN NOT NULL DEFAULT 0, + -- Chapter 2.7.6 + ms_purged_ps BOOLEAN NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS subscriber_apn ( + subscriber_id INTEGER, -- subscriber.id + apn VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS subscriber_multi_msisdn ( +-- Chapter 2.1.3 + subscriber_id INTEGER, -- subscriber.id + msisdn VARCHAR(15) NOT NULL +); + +CREATE TABLE IF NOT EXISTS auc_2g ( + subscriber_id INTEGER PRIMARY KEY, -- subscriber.id + algo_id_2g INTEGER NOT NULL, -- enum osmo_auth_algo value + ki VARCHAR(32) NOT NULL -- hex string: subscriber's secret key (128bit) +); + +CREATE TABLE IF NOT EXISTS auc_3g ( + subscriber_id INTEGER PRIMARY KEY, -- subscriber.id + algo_id_3g INTEGER NOT NULL, -- enum osmo_auth_algo value + k VARCHAR(32) NOT NULL, -- hex string: subscriber's secret key (128bit) + op VARCHAR(32), -- hex string: operator's secret key (128bit) + opc VARCHAR(32), -- hex string: derived from OP and K (128bit) + sqn INTEGER NOT NULL DEFAULT 0, -- sequence number of key usage + ind_bitlen INTEGER NOT NULL DEFAULT 5 -- nr of index bits at lower SQN end +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_subscr_imsi ON subscriber (imsi); diff --git a/sql/hlr_data.sql b/sql/hlr_data.sql new file mode 100644 index 0000000..0767d48 --- /dev/null +++ b/sql/hlr_data.sql @@ -0,0 +1,13 @@ + +-- 2G only subscriber +INSERT INTO subscriber (id, imsi) VALUES (1, '901990000000001'); +INSERT INTO auc_2g (subscriber_id, algo_id_2g, ki) VALUES (1, 1, '000102030405060708090a0b0c0d0e0f'); + +-- 3G only subscriber +INSERT INTO subscriber (id, imsi) VALUES (2, '901990000000002'); +INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, opc, sqn) VALUES (2, 5, '000102030405060708090a0b0c0d0e0f', '101112131415161718191a1b1c1d1e1f', 0); + +-- 2G + 3G subscriber +INSERT INTO subscriber (id, imsi) VALUES (3, '901990000000003'); +INSERT INTO auc_2g (subscriber_id, algo_id_2g, ki) VALUES (3, 1, '000102030405060708090a0b0c0d0e0f'); +INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, opc, sqn) VALUES (3, 5, '000102030405060708090a0b0c0d0e0f', '101112131415161718191a1b1c1d1e1f', 0); diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..9fbb062 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,109 @@ +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(SQLITE3_CFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + populate_hlr_db.pl \ + db_bootstrap.sed \ + $(NULL) + +BUILT_SOURCES = \ + db_bootstrap.h \ + $(NULL) +CLEANFILES = $(BUILT_SOURCES) + +noinst_HEADERS = \ + auc.h \ + db.h \ + hlr.h \ + luop.h \ + gsup_router.h \ + gsup_server.h \ + logging.h \ + rand.h \ + ctrl.h \ + hlr_vty.h \ + hlr_vty_subscr.h \ + db_bootstrap.h \ + $(NULL) + +bin_PROGRAMS = \ + osmo-hlr \ + osmo-hlr-db-tool \ + $(NULL) + +noinst_PROGRAMS = \ + db_test \ + $(NULL) + +osmo_hlr_SOURCES = \ + auc.c \ + ctrl.c \ + db.c \ + luop.c \ + db_auc.c \ + db_hlr.c \ + gsup_router.c \ + gsup_server.c \ + hlr.c \ + logging.c \ + rand_urandom.c \ + hlr_vty.c \ + hlr_vty_subscr.c \ + $(NULL) + +osmo_hlr_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(SQLITE3_LIBS) \ + $(NULL) + +osmo_hlr_db_tool_SOURCES = \ + hlr_db_tool.c \ + db.c \ + db_hlr.c \ + logging.c \ + rand_urandom.c \ + dbd_decode_binary.c \ + $(NULL) + +osmo_hlr_db_tool_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(SQLITE3_LIBS) \ + $(NULL) + +db_test_SOURCES = \ + auc.c \ + db.c \ + db_auc.c \ + db_test.c \ + logging.c \ + rand_fake.c \ + $(NULL) + +db_test_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(SQLITE3_LIBS) \ + $(NULL) + +BOOTSTRAP_SQL = $(top_srcdir)/sql/hlr.sql + +db_bootstrap.h: $(BOOTSTRAP_SQL) $(srcdir)/db_bootstrap.sed + echo "/* DO NOT EDIT THIS FILE. It is generated from osmo-hlr.git/sql/hlr.sql */" > "$@" + echo "#pragma once" >> "$@" + echo "static const char *stmt_bootstrap_sql[] = {" >> "$@" + cat "$(BOOTSTRAP_SQL)" \ + | sed -f "$(srcdir)/db_bootstrap.sed" \ + >> "$@" + echo "};" >> "$@" diff --git a/src/auc.c b/src/auc.c new file mode 100644 index 0000000..f55b377 --- /dev/null +++ b/src/auc.c @@ -0,0 +1,184 @@ +/* (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include + +#include "logging.h" +#include "rand.h" + +#define hexb(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf)) +#define hex(buf,sz) osmo_hexdump_nospc((void*)buf, sz) + +/* compute given number of vectors using either aud2g or aud2g or a combination + * of both. Handles re-synchronization if rand_auts and auts are set */ +int auc_compute_vectors(struct osmo_auth_vector *vec, unsigned int num_vec, + struct osmo_sub_auth_data *aud2g, + struct osmo_sub_auth_data *aud3g, + const uint8_t *rand_auts, const uint8_t *auts) +{ + unsigned int i; + uint8_t rand[16]; + struct osmo_auth_vector vtmp; + int rc; + + /* no need to iterate the log categories all the time */ + int dbg = log_check_level(DAUC, LOGL_DEBUG); +#define DBGP(args ...) if (dbg) DEBUGP(DAUC, ##args) +#define DBGVB(member) DBGP("vector [%u]: " #member " = %s\n", \ + i, hexb(vec[i].member)) +#define DBGVV(fmt, member) DBGP("vector [%u]: " #member " = " fmt "\n", \ + i, vec[i].member) + + if (aud2g && (aud2g->algo == OSMO_AUTH_ALG_NONE + || aud2g->type == OSMO_AUTH_TYPE_NONE)) + aud2g = NULL; + if (aud3g && (aud3g->algo == OSMO_AUTH_ALG_NONE + || aud3g->type == OSMO_AUTH_TYPE_NONE)) + aud3g = NULL; + + if (!aud2g && !aud3g) { + LOGP(DAUC, LOGL_ERROR, "auc_compute_vectors() called" + " with neither 2G nor 3G auth data available\n"); + return -1; + } + + if (aud2g && aud2g->type != OSMO_AUTH_TYPE_GSM) { + LOGP(DAUC, LOGL_ERROR, "auc_compute_vectors() called" + " with non-2G auth data passed for aud2g arg\n"); + return -1; + } + + if (aud3g && aud3g->type != OSMO_AUTH_TYPE_UMTS) { + LOGP(DAUC, LOGL_ERROR, "auc_compute_vectors() called" + " with non-3G auth data passed for aud3g arg\n"); + return -1; + } + + if ((rand_auts != NULL) != (auts != NULL)) { + LOGP(DAUC, LOGL_ERROR, "auc_compute_vectors() with only one" + " of AUTS and AUTS_RAND given, need both or neither\n"); + return -1; + } + + if (auts && !aud3g) { + LOGP(DAUC, LOGL_ERROR, "auc_compute_vectors() with AUTS called" + " but no 3G auth data passed\n"); + return -1; + } + + DBGP("Computing %d auth vector%s: %s%s\n", + num_vec, num_vec == 1 ? "" : "s", + aud3g? (aud2g? "3G + separate 2G" + : "3G only (2G derived from 3G keys)") + : "2G only", + auts? ", with AUTS resync" : ""); + if (aud3g) { + DBGP("3G: k = %s\n", hexb(aud3g->u.umts.k)); + DBGP("3G: %s = %s\n", + aud3g->u.umts.opc_is_op? "OP" : "opc", + hexb(aud3g->u.umts.opc)); + DBGP("3G: for sqn ind %u, previous sqn was %" PRIu64 "\n", + aud3g->u.umts.ind, aud3g->u.umts.sqn); + } + if (aud2g) + DBGP("2G: ki = %s\n", hexb(aud2g->u.gsm.ki)); + + for (i = 0; i < num_vec; i++) { + rc = rand_get(rand, sizeof(rand)); + if (rc != sizeof(rand)) { + LOGP(DAUC, LOGL_ERROR, "Unable to read %zu random " + "bytes: rc=%d\n", sizeof(rand), rc); + goto out; + } + DBGP("vector [%u]: rand = %s\n", i, hexb(rand)); + + if (aud3g) { + /* 3G or 3G + 2G case */ + + /* Do AUTS only for the first vector or we would use + * the same SQN for each following key. */ + if ((i == 0) && auts) { + DBGP("vector [%u]: resync: auts = %s\n", + i, hex(auts, 14)); + DBGP("vector [%u]: resync: rand_auts = %s\n", + i, hex(rand_auts, 16)); + + rc = osmo_auth_gen_vec_auts(vec+i, aud3g, auts, + rand_auts, rand); + } else { + rc = osmo_auth_gen_vec(vec+i, aud3g, rand); + } + if (rc < 0) { + LOGP(DAUC, LOGL_ERROR, "Error in 3G vector " + "generation: [%u]: rc = %d\n", i, rc); + goto out; + } + DBGP("vector [%u]: sqn = %" PRIu64 "\n", + i, aud3g->u.umts.sqn); + + DBGVB(autn); + DBGVB(ck); + DBGVB(ik); + DBGVB(res); + DBGVV("%u", res_len); + + if (!aud2g) { + /* use the 2G tokens from 3G keys */ + DBGVB(kc); + DBGVB(sres); + DBGVV("0x%x", auth_types); + continue; + } + /* calculate 2G separately */ + + DBGP("vector [%u]: deriving 2G from 3G\n", i); + + rc = osmo_auth_gen_vec(&vtmp, aud2g, rand); + if (rc < 0) { + LOGP(DAUC, LOGL_ERROR, "Error in 2G vector" + "generation: [%u]: rc = %d\n", i, rc); + goto out; + } + memcpy(&vec[i].kc, vtmp.kc, sizeof(vec[i].kc)); + memcpy(&vec[i].sres, vtmp.sres, sizeof(vec[i].sres)); + vec[i].auth_types |= OSMO_AUTH_TYPE_GSM; + } else { + /* 2G only case */ + rc = osmo_auth_gen_vec(vec+i, aud2g, rand); + if (rc < 0) { + LOGP(DAUC, LOGL_ERROR, "Error in 2G vector " + "generation: [%u]: rc = %d\n", i, rc); + goto out; + } + } + + DBGVB(kc); + DBGVB(sres); + DBGVV("0x%x", auth_types); + } +out: + return i; +#undef DBGVV +#undef DBGVB +#undef DBGP +} diff --git a/src/auc.h b/src/auc.h new file mode 100644 index 0000000..f5b6765 --- /dev/null +++ b/src/auc.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +int auc_compute_vectors(struct osmo_auth_vector *vec, unsigned int num_vec, + struct osmo_sub_auth_data *aud2g, + struct osmo_sub_auth_data *aud3g, + const uint8_t *rand_auts, const uint8_t *auts); diff --git a/src/ctrl.c b/src/ctrl.c new file mode 100644 index 0000000..3e81661 --- /dev/null +++ b/src/ctrl.c @@ -0,0 +1,398 @@ +/* OsmoHLR Control Interface implementation */ + +/* (C) 2017 sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Max Suraev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include + +#include "hlr.h" +#include "ctrl.h" +#include "db.h" + +#define SEL_BY "by-" +#define SEL_BY_IMSI SEL_BY "imsi-" +#define SEL_BY_MSISDN SEL_BY "msisdn-" +#define SEL_BY_ID SEL_BY "id-" + +#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf)) + +static bool startswith(const char *str, const char *start) +{ + return strncmp(str, start, strlen(start)) == 0; +} + +static int _get_subscriber(struct db_context *dbc, + const char *by_selector, + struct hlr_subscriber *subscr) +{ + const char *val; + if (startswith(by_selector, SEL_BY_IMSI)) { + val = by_selector + strlen(SEL_BY_IMSI); + if (!osmo_imsi_str_valid(val)) + return -EINVAL; + return db_subscr_get_by_imsi(dbc, val, subscr); + } + if (startswith(by_selector, SEL_BY_MSISDN)) { + val = by_selector + strlen(SEL_BY_MSISDN); + if (!osmo_msisdn_str_valid(val)) + return -EINVAL; + return db_subscr_get_by_msisdn(dbc, val, subscr); + } + if (startswith(by_selector, SEL_BY_ID)) { + int64_t id; + char *endptr; + val = by_selector + strlen(SEL_BY_ID); + if (*val == '+') + return -EINVAL; + errno = 0; + id = strtoll(val, &endptr, 10); + if (errno || *endptr) + return -EINVAL; + return db_subscr_get_by_id(dbc, id, subscr); + } + return -ENOTSUP; +} + +static bool get_subscriber(struct db_context *dbc, + const char *by_selector, + struct hlr_subscriber *subscr, + struct ctrl_cmd *cmd) +{ + int rc = _get_subscriber(dbc, by_selector, subscr); + switch (rc) { + case 0: + return true; + case -ENOTSUP: + cmd->reply = "Not a known subscriber 'by-xxx-' selector."; + return false; + case -EINVAL: + cmd->reply = "Invalid value part of 'by-xxx-value' selector."; + return false; + case -ENOENT: + cmd->reply = "No such subscriber."; + return false; + default: + cmd->reply = "An unknown error has occured during get_subscriber()."; + return false; + } +} + +/* Optimization: if a subscriber operation is requested by-imsi, just return + * the IMSI right back. */ +static const char *get_subscriber_imsi(struct db_context *dbc, + const char *by_selector, + struct ctrl_cmd *cmd) +{ + static struct hlr_subscriber subscr; + + if (startswith(by_selector, SEL_BY_IMSI)) + return by_selector + strlen(SEL_BY_IMSI); + if (!get_subscriber(dbc, by_selector, &subscr, cmd)) + return NULL; + return subscr.imsi; +} + +/* printf fmt and arg to completely omit a string if it is empty. */ +#define FMT_S "%s%s%s%s" +#define ARG_S(name, val) \ + (val) && *(val) ? "\n" : "", \ + (val) && *(val) ? name : "", \ + (val) && *(val) ? "\t" : "", \ + (val) && *(val) ? (val) : "" \ + +/* printf fmt and arg to completely omit bool of given value. */ +#define FMT_BOOL "%s" +#define ARG_BOOL(name, val) \ + val ? "\n" name "\t1" : "\n" name "\t0" + +static void print_subscr_info(struct ctrl_cmd *cmd, + struct hlr_subscriber *subscr) +{ + ctrl_cmd_reply_printf(cmd, + "\nid\t%"PRIu64 + FMT_S + FMT_S + FMT_BOOL + FMT_BOOL + FMT_S + FMT_S + FMT_S + FMT_BOOL + FMT_BOOL + "\nperiodic_lu_timer\t%u" + "\nperiodic_rau_tau_timer\t%u" + "\nlmsi\t%08x" + , + subscr->id, + ARG_S("imsi", subscr->imsi), + ARG_S("msisdn", subscr->msisdn), + ARG_BOOL("nam_cs", subscr->nam_cs), + ARG_BOOL("nam_ps", subscr->nam_ps), + ARG_S("vlr_number", subscr->vlr_number), + ARG_S("sgsn_number", subscr->sgsn_number), + ARG_S("sgsn_address", subscr->sgsn_address), + ARG_BOOL("ms_purged_cs", subscr->ms_purged_cs), + ARG_BOOL("ms_purged_ps", subscr->ms_purged_ps), + subscr->periodic_lu_timer, + subscr->periodic_rau_tau_timer, + subscr->lmsi + ); +} + +static void print_subscr_info_aud2g(struct ctrl_cmd *cmd, struct osmo_sub_auth_data *aud) +{ + if (aud->algo == OSMO_AUTH_ALG_NONE) + return; + ctrl_cmd_reply_printf(cmd, + "\naud2g.algo\t%s" + "\naud2g.ki\t%s" + , + osmo_auth_alg_name(aud->algo), + hexdump_buf(aud->u.gsm.ki)); +} + +static void print_subscr_info_aud3g(struct ctrl_cmd *cmd, struct osmo_sub_auth_data *aud) +{ + if (aud->algo == OSMO_AUTH_ALG_NONE) + return; + ctrl_cmd_reply_printf(cmd, + "\naud3g.algo\t%s" + "\naud3g.k\t%s" + , + osmo_auth_alg_name(aud->algo), + hexdump_buf(aud->u.umts.k)); + /* hexdump uses a static string buffer, hence only one hexdump per + * printf(). */ + ctrl_cmd_reply_printf(cmd, + "\naud3g.%s\t%s" + "\naud3g.ind_bitlen\t%u" + "\naud3g.sqn\t%"PRIu64 + , + aud->u.umts.opc_is_op? "op" : "opc", + hexdump_buf(aud->u.umts.opc), + aud->u.umts.ind_bitlen, + aud->u.umts.sqn); +} + +CTRL_CMD_DEFINE_RO(subscr_info, "info"); +static int get_subscr_info(struct ctrl_cmd *cmd, void *data) +{ + struct hlr_subscriber subscr; + struct hlr *hlr = data; + const char *by_selector = cmd->node; + + if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd)) + return CTRL_CMD_ERROR; + + print_subscr_info(cmd, &subscr); + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(subscr_info_aud, "info-aud"); +static int get_subscr_info_aud(struct ctrl_cmd *cmd, void *data) +{ + const char *imsi; + struct osmo_sub_auth_data aud2g; + struct osmo_sub_auth_data aud3g; + struct hlr *hlr = data; + const char *by_selector = cmd->node; + int rc; + + imsi = get_subscriber_imsi(hlr->dbc, by_selector, cmd); + if (!imsi) + return CTRL_CMD_ERROR; + + rc = db_get_auth_data(hlr->dbc, imsi, &aud2g, &aud3g, NULL); + + if (rc == -ENOENT) { + /* No auth data found, tell the print*() functions about it. */ + aud2g.algo = OSMO_AUTH_ALG_NONE; + aud3g.algo = OSMO_AUTH_ALG_NONE; + } else if (rc) { + cmd->reply = "Error retrieving authentication data."; + return CTRL_CMD_ERROR; + } + + print_subscr_info_aud2g(cmd, &aud2g); + print_subscr_info_aud3g(cmd, &aud3g); + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(subscr_info_all, "info-all"); +static int get_subscr_info_all(struct ctrl_cmd *cmd, void *data) +{ + struct hlr_subscriber subscr; + struct osmo_sub_auth_data aud2g; + struct osmo_sub_auth_data aud3g; + struct hlr *hlr = data; + const char *by_selector = cmd->node; + int rc; + + if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd)) + return CTRL_CMD_ERROR; + + rc = db_get_auth_data(hlr->dbc, subscr.imsi, &aud2g, &aud3g, NULL); + + if (rc == -ENOENT) { + /* No auth data found, tell the print*() functions about it. */ + aud2g.algo = OSMO_AUTH_ALG_NONE; + aud3g.algo = OSMO_AUTH_ALG_NONE; + } else if (rc) { + cmd->reply = "Error retrieving authentication data."; + return CTRL_CMD_ERROR; + } + + print_subscr_info(cmd, &subscr); + print_subscr_info_aud2g(cmd, &aud2g); + print_subscr_info_aud3g(cmd, &aud3g); + + return CTRL_CMD_REPLY; +} + +static int verify_subscr_cs_ps_enabled(struct ctrl_cmd *cmd, const char *value, void *data) +{ + if (!value || !*value + || (strcmp(value, "0") && strcmp(value, "1"))) + return 1; + return 0; +} + +static int get_subscr_cs_ps_enabled(struct ctrl_cmd *cmd, void *data, + bool is_ps) +{ + struct hlr_subscriber subscr; + struct hlr *hlr = data; + const char *by_selector = cmd->node; + + if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd)) + return CTRL_CMD_ERROR; + + cmd->reply = (is_ps ? subscr.nam_ps : subscr.nam_cs) + ? "1" : "0"; + return CTRL_CMD_REPLY; +} + +static int set_subscr_cs_ps_enabled(struct ctrl_cmd *cmd, void *data, + bool is_ps) +{ + const char *imsi; + struct hlr *hlr = data; + const char *by_selector = cmd->node; + + imsi = get_subscriber_imsi(hlr->dbc, by_selector, cmd); + if (!imsi) + return CTRL_CMD_ERROR; + if (db_subscr_nam(hlr->dbc, imsi, strcmp(cmd->value, "1") == 0, is_ps)) + return CTRL_CMD_ERROR; + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(subscr_ps_enabled, "ps-enabled"); +static int verify_subscr_ps_enabled(struct ctrl_cmd *cmd, const char *value, void *data) +{ + return verify_subscr_cs_ps_enabled(cmd, value, data); +} +static int get_subscr_ps_enabled(struct ctrl_cmd *cmd, void *data) +{ + return get_subscr_cs_ps_enabled(cmd, data, true); +} +static int set_subscr_ps_enabled(struct ctrl_cmd *cmd, void *data) +{ + return set_subscr_cs_ps_enabled(cmd, data, true); +} + +CTRL_CMD_DEFINE(subscr_cs_enabled, "cs-enabled"); +static int verify_subscr_cs_enabled(struct ctrl_cmd *cmd, const char *value, void *data) +{ + return verify_subscr_cs_ps_enabled(cmd, value, data); +} +static int get_subscr_cs_enabled(struct ctrl_cmd *cmd, void *data) +{ + return get_subscr_cs_ps_enabled(cmd, data, false); +} +static int set_subscr_cs_enabled(struct ctrl_cmd *cmd, void *data) +{ + return set_subscr_cs_ps_enabled(cmd, data, false); +} + +int hlr_ctrl_cmds_install() +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_info); + rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_info_aud); + rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_info_all); + rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_ps_enabled); + rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_cs_enabled); + + return rc; +} + +static int hlr_ctrl_node_lookup(void *data, vector vline, int *node_type, + void **node_data, int *i) +{ + const char *token = vector_slot(vline, *i); + + switch (*node_type) { + case CTRL_NODE_ROOT: + if (strcmp(token, "subscriber") != 0) + return 0; + *node_data = NULL; + *node_type = CTRL_NODE_SUBSCR; + break; + case CTRL_NODE_SUBSCR: + if (!startswith(token, "by-")) + return 0; + *node_data = (void*)token; + *node_type = CTRL_NODE_SUBSCR_BY; + break; + default: + return 0; + } + + return 1; +} + +struct ctrl_handle *hlr_controlif_setup(struct hlr *hlr) +{ + int rc; + struct ctrl_handle *hdl = ctrl_interface_setup_dynip2(hlr, + hlr->ctrl_bind_addr, + OSMO_CTRL_PORT_HLR, + hlr_ctrl_node_lookup, + _LAST_CTRL_NODE_HLR); + if (!hdl) + return NULL; + + rc = hlr_ctrl_cmds_install(); + if (rc) /* FIXME: close control interface? */ + return NULL; + + return hdl; +} diff --git a/src/ctrl.h b/src/ctrl.h new file mode 100644 index 0000000..3f9ba3f --- /dev/null +++ b/src/ctrl.h @@ -0,0 +1,34 @@ +/* OsmoHLR Control Interface implementation */ + +/* (C) 2017 sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Max Suraev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include + +enum hlr_ctrl_node { + CTRL_NODE_SUBSCR = _LAST_CTRL_NODE, + CTRL_NODE_SUBSCR_BY, + _LAST_CTRL_NODE_HLR +}; + +int hlr_ctrl_cmds_install(); +struct ctrl_handle *hlr_controlif_setup(struct hlr *hlr); diff --git a/src/db.c b/src/db.c new file mode 100644 index 0000000..8733cf5 --- /dev/null +++ b/src/db.c @@ -0,0 +1,280 @@ +/* (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include +#include + +#include "logging.h" +#include "db.h" +#include "db_bootstrap.h" + +#define SEL_COLUMNS \ + "id," \ + "imsi," \ + "msisdn," \ + "vlr_number," \ + "sgsn_number," \ + "sgsn_address," \ + "periodic_lu_tmr," \ + "periodic_rau_tau_tmr," \ + "nam_cs," \ + "nam_ps," \ + "lmsi," \ + "ms_purged_cs," \ + "ms_purged_ps" + +static const char *stmt_sql[] = { + [DB_STMT_SEL_BY_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi = ?", + [DB_STMT_SEL_BY_MSISDN] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE msisdn = ?", + [DB_STMT_SEL_BY_ID] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE id = ?", + [DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number WHERE id = $subscriber_id", + [DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number WHERE id = $subscriber_id", + [DB_STMT_AUC_BY_IMSI] = + "SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn, ind_bitlen" + " FROM subscriber" + " LEFT JOIN auc_2g ON auc_2g.subscriber_id = subscriber.id" + " LEFT JOIN auc_3g ON auc_3g.subscriber_id = subscriber.id" + " WHERE imsi = $imsi", + [DB_STMT_AUC_UPD_SQN] = "UPDATE auc_3g SET sqn = $sqn WHERE subscriber_id = $subscriber_id", + [DB_STMT_UPD_PURGE_CS_BY_IMSI] = "UPDATE subscriber SET ms_purged_cs = $val WHERE imsi = $imsi", + [DB_STMT_UPD_PURGE_PS_BY_IMSI] = "UPDATE subscriber SET ms_purged_ps = $val WHERE imsi = $imsi", + [DB_STMT_UPD_NAM_CS_BY_IMSI] = "UPDATE subscriber SET nam_cs = $val WHERE imsi = $imsi", + [DB_STMT_UPD_NAM_PS_BY_IMSI] = "UPDATE subscriber SET nam_ps = $val WHERE imsi = $imsi", + [DB_STMT_SUBSCR_CREATE] = "INSERT INTO subscriber (imsi) VALUES ($imsi)", + [DB_STMT_DEL_BY_ID] = "DELETE FROM subscriber WHERE id = $subscriber_id", + [DB_STMT_SET_MSISDN_BY_IMSI] = "UPDATE subscriber SET msisdn = $msisdn WHERE imsi = $imsi", + [DB_STMT_AUC_2G_INSERT] = + "INSERT INTO auc_2g (subscriber_id, algo_id_2g, ki)" + " VALUES($subscriber_id, $algo_id_2g, $ki)", + [DB_STMT_AUC_2G_DELETE] = "DELETE FROM auc_2g WHERE subscriber_id = $subscriber_id", + [DB_STMT_AUC_3G_INSERT] = + "INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, op, opc, ind_bitlen)" + " VALUES($subscriber_id, $algo_id_3g, $k, $op, $opc, $ind_bitlen)", + [DB_STMT_AUC_3G_DELETE] = "DELETE FROM auc_3g WHERE subscriber_id = $subscriber_id", +}; + +static void sql3_error_log_cb(void *arg, int err_code, const char *msg) +{ + LOGP(DDB, LOGL_ERROR, "(%d) %s\n", err_code, msg); +} + +static void sql3_sql_log_cb(void *arg, sqlite3 *s3, const char *stmt, int type) +{ + switch (type) { + case 0: + LOGP(DDB, LOGL_DEBUG, "Opened database\n"); + break; + case 1: + LOGP(DDB, LOGL_DEBUG, "%s\n", stmt); + break; + case 2: + LOGP(DDB, LOGL_DEBUG, "Closed database\n"); + break; + default: + LOGP(DDB, LOGL_DEBUG, "Unknown %d\n", type); + break; + } +} + +/* remove bindings and reset statement to be re-executed */ +void db_remove_reset(sqlite3_stmt *stmt) +{ + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); +} + +/** bind text arg and do proper cleanup in case of failure. If param_name is + * NULL, bind to the first parameter (useful for SQL statements that have only + * one parameter). */ +bool db_bind_text(sqlite3_stmt *stmt, const char *param_name, const char *text) +{ + int rc; + int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1; + if (idx < 1) { + LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n", + param_name); + return false; + } + rc = sqlite3_bind_text(stmt, idx, text, -1, SQLITE_STATIC); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Error binding text to SQL parameter %s: %d\n", + param_name ? param_name : "#1", rc); + db_remove_reset(stmt); + return false; + } + return true; +} + +/** bind int arg and do proper cleanup in case of failure. If param_name is + * NULL, bind to the first parameter (useful for SQL statements that have only + * one parameter). */ +bool db_bind_int(sqlite3_stmt *stmt, const char *param_name, int nr) +{ + int rc; + int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1; + if (idx < 1) { + LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n", + param_name); + return false; + } + rc = sqlite3_bind_int(stmt, idx, nr); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Error binding int64 to SQL parameter %s: %d\n", + param_name ? param_name : "#1", rc); + db_remove_reset(stmt); + return false; + } + return true; +} + +/** bind int64 arg and do proper cleanup in case of failure. If param_name is + * NULL, bind to the first parameter (useful for SQL statements that have only + * one parameter). */ +bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr) +{ + int rc; + int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1; + if (idx < 1) { + LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n", + param_name); + return false; + } + rc = sqlite3_bind_int64(stmt, idx, nr); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Error binding int64 to SQL parameter %s: %d\n", + param_name ? param_name : "#1", rc); + db_remove_reset(stmt); + return false; + } + return true; +} + +void db_close(struct db_context *dbc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dbc->stmt); i++) { + /* it is ok to call finalize on NULL */ + sqlite3_finalize(dbc->stmt[i]); + } + sqlite3_close(dbc->db); + talloc_free(dbc); +} + +static int db_bootstrap(struct db_context *dbc) +{ + int i; + for (i = 0; i < ARRAY_SIZE(stmt_bootstrap_sql); i++) { + int rc; + sqlite3_stmt *stmt; + + rc = sqlite3_prepare_v2(dbc->db, stmt_bootstrap_sql[i], -1, + &stmt, NULL); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", + stmt_bootstrap_sql[i]); + return -1; + } + + /* execute the statement */ + rc = sqlite3_step(stmt); + db_remove_reset(stmt); + if (rc != SQLITE_DONE) { + LOGP(DDB, LOGL_ERROR, "Cannot bootstrap database: SQL error: (%d) %s," + " during stmt '%s'", + rc, sqlite3_errmsg(dbc->db), + stmt_bootstrap_sql[i]); + return -1; + } + } + return 0; +} + +struct db_context *db_open(void *ctx, const char *fname) +{ + struct db_context *dbc = talloc_zero(ctx, struct db_context); + unsigned int i; + int rc; + bool has_sqlite_config_sqllog = false; + + LOGP(DDB, LOGL_NOTICE, "using database: %s\n", fname); + LOGP(DDB, LOGL_INFO, "Compiled against SQLite3 lib version %s\n", SQLITE_VERSION); + LOGP(DDB, LOGL_INFO, "Running with SQLite3 lib version %s\n", sqlite3_libversion()); + + dbc->fname = talloc_strdup(dbc, fname); + + for (i = 0; i < 0xfffff; i++) { + const char *o = sqlite3_compileoption_get(i); + if (!o) + break; + LOGP(DDB, LOGL_DEBUG, "SQLite3 compiled with '%s'\n", o); + if (!strcmp(o, "ENABLE_SQLLOG")) + has_sqlite_config_sqllog = true; + } + + rc = sqlite3_config(SQLITE_CONFIG_LOG, sql3_error_log_cb, NULL); + if (rc != SQLITE_OK) + LOGP(DDB, LOGL_NOTICE, "Unable to set SQLite3 error log callback\n"); + + if (has_sqlite_config_sqllog) { + rc = sqlite3_config(SQLITE_CONFIG_SQLLOG, sql3_sql_log_cb, NULL); + if (rc != SQLITE_OK) + LOGP(DDB, LOGL_NOTICE, "Unable to set SQLite3 SQL log callback\n"); + } else + LOGP(DDB, LOGL_DEBUG, "Not setting SQL log callback:" + " SQLite3 compiled without support for it\n"); + + rc = sqlite3_open(dbc->fname, &dbc->db); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to open DB; rc = %d\n", rc); + talloc_free(dbc); + return NULL; + } + + /* enable extended result codes */ + rc = sqlite3_extended_result_codes(dbc->db, 1); + if (rc != SQLITE_OK) + LOGP(DDB, LOGL_ERROR, "Unable to enable SQLite3 extended result codes\n"); + + char *err_msg; + rc = sqlite3_exec(dbc->db, "PRAGMA journal_mode=WAL; PRAGMA synchonous = NORMAL;", 0, 0, &err_msg); + if (rc != SQLITE_OK) + LOGP(DDB, LOGL_ERROR, "Unable to set Write-Ahead Logging: %s\n", + err_msg); + + db_bootstrap(dbc); + + /* prepare all SQL statements */ + for (i = 0; i < ARRAY_SIZE(dbc->stmt); i++) { + rc = sqlite3_prepare_v2(dbc->db, stmt_sql[i], -1, + &dbc->stmt[i], NULL); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", stmt_sql[i]); + goto out_free; + } + } + + return dbc; +out_free: + db_close(dbc); + return NULL; +} diff --git a/src/db.h b/src/db.h new file mode 100644 index 0000000..fc8e511 --- /dev/null +++ b/src/db.h @@ -0,0 +1,142 @@ +#pragma once + +#include +#include + +struct hlr; + +enum stmt_idx { + DB_STMT_SEL_BY_IMSI, + DB_STMT_SEL_BY_MSISDN, + DB_STMT_SEL_BY_ID, + DB_STMT_UPD_VLR_BY_ID, + DB_STMT_UPD_SGSN_BY_ID, + DB_STMT_AUC_BY_IMSI, + DB_STMT_AUC_UPD_SQN, + DB_STMT_UPD_PURGE_CS_BY_IMSI, + DB_STMT_UPD_PURGE_PS_BY_IMSI, + DB_STMT_UPD_NAM_PS_BY_IMSI, + DB_STMT_UPD_NAM_CS_BY_IMSI, + DB_STMT_SUBSCR_CREATE, + DB_STMT_DEL_BY_ID, + DB_STMT_SET_MSISDN_BY_IMSI, + DB_STMT_AUC_2G_INSERT, + DB_STMT_AUC_2G_DELETE, + DB_STMT_AUC_3G_INSERT, + DB_STMT_AUC_3G_DELETE, + _NUM_DB_STMT +}; + +struct db_context { + char *fname; + sqlite3 *db; + sqlite3_stmt *stmt[_NUM_DB_STMT]; +}; + +void db_remove_reset(sqlite3_stmt *stmt); +bool db_bind_text(sqlite3_stmt *stmt, const char *param_name, const char *text); +bool db_bind_int(sqlite3_stmt *stmt, const char *param_name, int nr); +bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr); +void db_close(struct db_context *dbc); +struct db_context *db_open(void *ctx, const char *fname); + +#include + +/* obtain the authentication data for a given imsi */ +int db_get_auth_data(struct db_context *dbc, const char *imsi, + struct osmo_sub_auth_data *aud2g, + struct osmo_sub_auth_data *aud3g, + int64_t *subscr_id); + +int db_update_sqn(struct db_context *dbc, int64_t id, + uint64_t new_sqn); + +int db_get_auc(struct db_context *dbc, const char *imsi, + unsigned int auc_3g_ind, struct osmo_auth_vector *vec, + unsigned int num_vec, const uint8_t *rand_auts, + const uint8_t *auts); + +#include +#include + +/* TODO: Get this from somewhere? */ +#define GT_MAX_DIGITS 15 + +struct hlr_subscriber { + struct llist_head list; + + int64_t id; + char imsi[GSM23003_IMSI_MAX_DIGITS+1]; + char msisdn[GT_MAX_DIGITS+1]; + /* imeisv? */ + char vlr_number[GT_MAX_DIGITS+1]; + char sgsn_number[GT_MAX_DIGITS+1]; + char sgsn_address[GT_MAX_DIGITS+1]; + /* ggsn number + address */ + /* gmlc number */ + /* smsc number */ + uint32_t periodic_lu_timer; + uint32_t periodic_rau_tau_timer; + bool nam_cs; + bool nam_ps; + uint32_t lmsi; + bool ms_purged_cs; + bool ms_purged_ps; +}; + +/* Like struct osmo_sub_auth_data, but the keys are in hexdump representation. + * This is useful because SQLite requires them in hexdump format, and callers + * like the VTY and CTRL interface also have them available as hexdump to begin + * with. In the binary format, a VTY command would first need to hexparse, + * after which the db function would again hexdump, copying to separate + * buffers. The roundtrip can be saved by providing char* to begin with. */ +struct sub_auth_data_str { + enum osmo_sub_auth_type type; + enum osmo_auth_algo algo; + union { + struct { + const char *opc; + const char *k; + uint64_t sqn; + int opc_is_op; + unsigned int ind_bitlen; + } umts; + struct { + const char *ki; + } gsm; + } u; +}; + +int db_subscr_create(struct db_context *dbc, const char *imsi); +int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id); + +int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi, + const char *msisdn); +int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id, + const struct sub_auth_data_str *aud); + +int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi, + struct hlr_subscriber *subscr); +int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn, + struct hlr_subscriber *subscr); +int db_subscr_get_by_id(struct db_context *dbc, int64_t id, + struct hlr_subscriber *subscr); +int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps); +int db_subscr_lu(struct db_context *dbc, int64_t subscr_id, + const char *vlr_or_sgsn_number, bool is_ps); + +int db_subscr_purge(struct db_context *dbc, const char *by_imsi, + bool purge_val, bool is_ps); + +int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps); + +/*! Call sqlite3_column_text() and copy result to a char[]. + * \param[out] buf A char[] used as sizeof() arg(!) and osmo_strlcpy() target. + * \param[in] stmt An sqlite3_stmt*. + * \param[in] idx Index in stmt's returned columns. + */ +#define copy_sqlite3_text_to_buf(buf, stmt, idx) \ + do { \ + const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \ + osmo_strlcpy(buf, _txt, sizeof(buf)); \ + } while (0) diff --git a/src/db_auc.c b/src/db_auc.c new file mode 100644 index 0000000..7bbc93f --- /dev/null +++ b/src/db_auc.c @@ -0,0 +1,225 @@ +/* (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include + +#include + +#include "logging.h" +#include "db.h" +#include "auc.h" +#include "rand.h" + +#define LOGAUC(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args) + +/* update the SQN for a given subscriber ID */ +int db_update_sqn(struct db_context *dbc, int64_t subscr_id, uint64_t new_sqn) +{ + sqlite3_stmt *stmt = dbc->stmt[DB_STMT_AUC_UPD_SQN]; + int rc; + int ret = 0; + + if (!db_bind_int64(stmt, "$sqn", new_sqn)) + return -EIO; + + if (!db_bind_int64(stmt, "$subscriber_id", subscr_id)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + LOGP(DAUC, LOGL_ERROR, "Cannot update SQN for subscriber ID=%"PRId64 + ": SQL error: (%d) %s\n", + subscr_id, rc, sqlite3_errmsg(dbc->db)); + ret = -EIO; + goto out; + } + + /* verify execution result */ + rc = sqlite3_changes(dbc->db); + if (!rc) { + LOGP(DAUC, LOGL_ERROR, "Cannot update SQN for subscriber ID=%"PRId64 + ": no auc_3g entry for such subscriber\n", subscr_id); + ret = -ENOENT; + } else if (rc != 1) { + LOGP(DAUC, LOGL_ERROR, "Update SQN for subscriber ID=%"PRId64 + ": SQL modified %d rows (expected 1)\n", subscr_id, rc); + ret = -EIO; + } + +out: + db_remove_reset(stmt); + return ret; +} + +/* obtain the authentication data for a given imsi + * returns -1 in case of error, 0 for unknown IMSI, 1 for success */ +int db_get_auth_data(struct db_context *dbc, const char *imsi, + struct osmo_sub_auth_data *aud2g, + struct osmo_sub_auth_data *aud3g, + int64_t *subscr_id) +{ + sqlite3_stmt *stmt = dbc->stmt[DB_STMT_AUC_BY_IMSI]; + int ret = 0; + int rc; + + memset(aud2g, 0, sizeof(*aud2g)); + memset(aud3g, 0, sizeof(*aud3g)); + + if (!db_bind_text(stmt, "$imsi", imsi)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc == SQLITE_DONE) { + LOGAUC(imsi, LOGL_INFO, "No such subscriber\n"); + ret = -ENOENT; + goto out; + } else if (rc != SQLITE_ROW) { + LOGAUC(imsi, LOGL_ERROR, "Error executing SQL: %d\n", rc); + ret = -EIO; + goto out; + } + + /* as an optimization, we retrieve the subscriber ID, to ensure we can + * update the SQN later without having to go back via a JOIN with the + * subscriber table. */ + if (subscr_id) + *subscr_id = sqlite3_column_int64(stmt, 0); + + /* obtain result values using sqlite3_column_*() */ + if (sqlite3_column_type(stmt, 1) == SQLITE_INTEGER) { + /* we do have some 2G authentication data */ + const uint8_t *ki; + + aud2g->algo = sqlite3_column_int(stmt, 1); + ki = sqlite3_column_text(stmt, 2); +#if 0 + if (sqlite3_column_bytes(stmt, 2) != sizeof(aud2g->u.gsm.ki)) { + LOGAUC(imsi, LOGL_ERROR, "Error reading Ki: %d\n", rc); + goto end_2g; + } +#endif + osmo_hexparse((void*)ki, (void*)&aud2g->u.gsm.ki, sizeof(aud2g->u.gsm.ki)); + aud2g->type = OSMO_AUTH_TYPE_GSM; + } else + LOGAUC(imsi, LOGL_DEBUG, "No 2G Auth Data\n"); +//end_2g: + if (sqlite3_column_type(stmt, 3) == SQLITE_INTEGER) { + /* we do have some 3G authentication data */ + const uint8_t *k, *op, *opc; + + aud3g->algo = sqlite3_column_int(stmt, 3); + k = sqlite3_column_text(stmt, 4); + if (!k) { + LOGAUC(imsi, LOGL_ERROR, "Error reading K: %d\n", rc); + ret = -EIO; + goto out; + } + osmo_hexparse((void*)k, (void*)&aud3g->u.umts.k, sizeof(aud3g->u.umts.k)); + /* UMTS Subscribers can have either OP or OPC */ + op = sqlite3_column_text(stmt, 5); + if (!op) { + opc = sqlite3_column_text(stmt, 6); + if (!opc) { + LOGAUC(imsi, LOGL_ERROR, "Error reading OPC: %d\n", rc); + ret = -EIO; + goto out; + } + osmo_hexparse((void*)opc, (void*)&aud3g->u.umts.opc, + sizeof(aud3g->u.umts.opc)); + aud3g->u.umts.opc_is_op = 0; + } else { + osmo_hexparse((void*)op, (void*)&aud3g->u.umts.opc, + sizeof(aud3g->u.umts.opc)); + aud3g->u.umts.opc_is_op = 1; + } + aud3g->u.umts.sqn = sqlite3_column_int64(stmt, 7); + aud3g->u.umts.ind_bitlen = sqlite3_column_int(stmt, 8); + /* FIXME: amf? */ + aud3g->type = OSMO_AUTH_TYPE_UMTS; + } else + LOGAUC(imsi, LOGL_DEBUG, "No 3G Auth Data\n"); + + if (aud2g->type == 0 && aud3g->type == 0) + ret = -ENOENT; + +out: + db_remove_reset(stmt); + return ret; +} + +/* return -1 in case of error, 0 for unknown imsi, positive for number + * of vectors generated */ +int db_get_auc(struct db_context *dbc, const char *imsi, + unsigned int auc_3g_ind, struct osmo_auth_vector *vec, + unsigned int num_vec, const uint8_t *rand_auts, + const uint8_t *auts) +{ + struct osmo_sub_auth_data aud2g, aud3g; + int64_t subscr_id; + int ret = 0; + int rc; + + rc = db_get_auth_data(dbc, imsi, &aud2g, &aud3g, &subscr_id); + if (rc) + return rc; + + aud3g.u.umts.ind = auc_3g_ind; + if (aud3g.type == OSMO_AUTH_TYPE_UMTS + && aud3g.u.umts.ind >= (1U << aud3g.u.umts.ind_bitlen)) { + LOGAUC(imsi, LOGL_NOTICE, "3G auth: SQN's IND bitlen %u is" + " too small to hold an index of %u. Truncating. This" + " may cause numerous additional AUTS resyncing.\n", + aud3g.u.umts.ind_bitlen, aud3g.u.umts.ind); + aud3g.u.umts.ind &= (1U << aud3g.u.umts.ind_bitlen) - 1; + } + + LOGAUC(imsi, LOGL_DEBUG, "Calling to generate %u vectors\n", num_vec); + rc = auc_compute_vectors(vec, num_vec, &aud2g, &aud3g, rand_auts, auts); + if (rc < 0) { + num_vec = 0; + ret = -1; + } else { + num_vec = rc; + ret = num_vec; + } + LOGAUC(imsi, LOGL_INFO, "Generated %u vectors\n", num_vec); + + /* Update SQN in database, as needed */ + if (aud3g.algo) { + LOGAUC(imsi, LOGL_DEBUG, "Updating SQN=%" PRIu64 " in DB\n", + aud3g.u.umts.sqn); + rc = db_update_sqn(dbc, subscr_id, aud3g.u.umts.sqn); + /* don't tell caller we generated any triplets in case of + * update error */ + if (rc < 0) { + LOGAUC(imsi, LOGL_ERROR, "Error updating SQN: %d\n", rc); + num_vec = 0; + ret = -1; + } + } + + return ret; +} diff --git a/src/db_bootstrap.sed b/src/db_bootstrap.sed new file mode 100644 index 0000000..60b8243 --- /dev/null +++ b/src/db_bootstrap.sed @@ -0,0 +1,25 @@ +# Input to this is sql/hlr.sql. +# +# We want each SQL statement line wrapped in "...\n", and each end (";") to +# become a comma: +# +# SOME SQL COMMAND ( +# that may span ) +# MULTIPLE LINES; +# MORE; +# +# --> +# +# "SOME SQL COMMAND (\n" +# " that may span )\n" +# "MULTIPLE LINES\n", <--note the comma here +# "MORE\n", +# +# just replacing ';' with '\n,' won't work, since sed is bad in printing +# multiple lines. Also, how to input newlines to sed is not portable across +# platforms. + +# Match excluding a trailing ';' as \1, keep any trailing ';' in \2 +s/^\(.*[^;]\)\(;\|\)$/"\1\\n"\2/ +# Replace trailing ';' as ',' +s/;$/,/ diff --git a/src/db_hlr.c b/src/db_hlr.c new file mode 100644 index 0000000..c4d4974 --- /dev/null +++ b/src/db_hlr.c @@ -0,0 +1,717 @@ +/* (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include + +#include + +#include "logging.h" +#include "hlr.h" +#include "db.h" +#include "gsup_server.h" +#include "luop.h" + +#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args) + +/*! Add new subscriber record to the HLR database. + * \param[in,out] dbc database context. + * \param[in] imsi ASCII string of IMSI digits, is validated. + * \returns 0 on success, -EINVAL on invalid IMSI, -EIO on database error. + */ +int db_subscr_create(struct db_context *dbc, const char *imsi) +{ + sqlite3_stmt *stmt; + int rc; + + if (!osmo_imsi_str_valid(imsi)) { + LOGP(DAUC, LOGL_ERROR, "Cannot create subscriber: invalid IMSI: '%s'\n", + imsi); + return -EINVAL; + } + + stmt = dbc->stmt[DB_STMT_SUBSCR_CREATE]; + + if (!db_bind_text(stmt, "$imsi", imsi)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + db_remove_reset(stmt); + if (rc != SQLITE_DONE) { + LOGHLR(imsi, LOGL_ERROR, "Cannot create subscriber: SQL error: (%d) %s\n", + rc, sqlite3_errmsg(dbc->db)); + return -EIO; + } + + return 0; +} + +/*! Completely delete a subscriber record from the HLR database. + * Also remove authentication data. + * Future todo: also drop from all other database tables, which aren't used yet + * at the time of writing this. + * \param[in,out] dbc database context. + * \param[in] subscr_id ID of the subscriber in the HLR db. + * \returns if the subscriber was found and removed, -EIO on database error, + * -ENOENT if no such subscriber data exists. + */ +int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id) +{ + int rc; + struct sub_auth_data_str aud; + int ret = 0; + + sqlite3_stmt *stmt = dbc->stmt[DB_STMT_DEL_BY_ID]; + + if (!db_bind_int64(stmt, "$subscriber_id", subscr_id)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + LOGP(DAUC, LOGL_ERROR, + "Cannot delete subscriber ID=%"PRId64": SQL error: (%d) %s\n", + subscr_id, rc, sqlite3_errmsg(dbc->db)); + db_remove_reset(stmt); + return -EIO; + } + + /* verify execution result */ + rc = sqlite3_changes(dbc->db); + if (!rc) { + LOGP(DAUC, LOGL_ERROR, "Cannot delete: no such subscriber: ID=%"PRId64"\n", + subscr_id); + ret = -ENOENT; + } else if (rc != 1) { + LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%"PRId64 + ": SQL modified %d rows (expected 1)\n", subscr_id, rc); + ret = -EIO; + } + db_remove_reset(stmt); + + /* make sure to remove authentication data for this subscriber id, for + * both 2G and 3G. */ + + aud = (struct sub_auth_data_str){ + .type = OSMO_AUTH_TYPE_GSM, + .algo = OSMO_AUTH_ALG_NONE, + }; + rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud); + if (ret == -ENOENT && !rc) + ret = 0; + + aud = (struct sub_auth_data_str){ + .type = OSMO_AUTH_TYPE_UMTS, + .algo = OSMO_AUTH_ALG_NONE, + }; + rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud); + if (ret == -ENOENT && !rc) + ret = 0; + + return ret; +} + +/*! Set a subscriber's MSISDN in the HLR database. + * \param[in,out] dbc database context. + * \param[in] imsi ASCII string of IMSI digits. + * \param[in] msisdn ASCII string of MSISDN digits. + * \returns 0 on success, -EINVAL in case of invalid MSISDN string, -EIO on + * database failure, -ENOENT if no such subscriber exists. + */ +int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi, + const char *msisdn) +{ + int rc; + int ret = 0; + + if (!osmo_msisdn_str_valid(msisdn)) { + LOGHLR(imsi, LOGL_ERROR, + "Cannot update subscriber: invalid MSISDN: '%s'\n", + msisdn); + return -EINVAL; + } + + sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SET_MSISDN_BY_IMSI]; + + if (!db_bind_text(stmt, "$imsi", imsi)) + return -EIO; + if (!db_bind_text(stmt, "$msisdn", msisdn)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + LOGHLR(imsi, LOGL_ERROR, + "Cannot update subscriber's MSISDN: SQL error: (%d) %s\n", + rc, sqlite3_errmsg(dbc->db)); + ret = -EIO; + goto out; + } + + /* verify execution result */ + rc = sqlite3_changes(dbc->db); + if (!rc) { + LOGP(DAUC, LOGL_ERROR, "Cannot update MSISDN: no such subscriber: IMSI='%s'\n", + imsi); + ret = -ENOENT; + goto out; + } else if (rc != 1) { + LOGHLR(imsi, LOGL_ERROR, "Update MSISDN: SQL modified %d rows (expected 1)\n", rc); + ret = -EIO; + } + +out: + db_remove_reset(stmt); + return ret; + +} + +/*! Insert or update 2G or 3G authentication tokens in the database. + * If aud->type is OSMO_AUTH_TYPE_GSM, the auc_2g table entry for the + * subscriber will be added or modified; if aud->algo is OSMO_AUTH_ALG_NONE, + * however, the auc_2g entry for the subscriber is deleted. If aud->type is + * OSMO_AUTH_TYPE_UMTS, the auc_3g table is updated; again, if aud->algo is + * OSMO_AUTH_ALG_NONE, the auc_3g entry is deleted. + * \param[in,out] dbc database context. + * \param[in] subscr_id DB ID of the subscriber. + * \param[in] aud Pointer to new auth data (in ASCII string form). + * \returns 0 on success, -EINVAL for invalid aud, -ENOENT for unknown + * subscr_id, -EIO for database errors. + */ +int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id, + const struct sub_auth_data_str *aud) +{ + sqlite3_stmt *stmt_del; + sqlite3_stmt *stmt_ins; + sqlite3_stmt *stmt; + const char *label; + int rc; + int ret = 0; + + switch (aud->type) { + case OSMO_AUTH_TYPE_GSM: + label = "auc_2g"; + stmt_del = dbc->stmt[DB_STMT_AUC_2G_DELETE]; + stmt_ins = dbc->stmt[DB_STMT_AUC_2G_INSERT]; + + switch (aud->algo) { + case OSMO_AUTH_ALG_NONE: + case OSMO_AUTH_ALG_COMP128v1: + case OSMO_AUTH_ALG_COMP128v2: + case OSMO_AUTH_ALG_COMP128v3: + case OSMO_AUTH_ALG_XOR: + break; + case OSMO_AUTH_ALG_MILENAGE: + LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:" + " auth algo not suited for 2G: %s\n", + osmo_auth_alg_name(aud->algo)); + return -EINVAL; + default: + LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:" + " Unknown auth algo: %d\n", aud->algo); + return -EINVAL; + } + + if (aud->algo == OSMO_AUTH_ALG_NONE) + break; + if (!osmo_is_hexstr(aud->u.gsm.ki, 32, 32, true)) { + LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:" + " Invalid KI: '%s'\n", aud->u.gsm.ki); + return -EINVAL; + } + break; + + case OSMO_AUTH_TYPE_UMTS: + label = "auc_3g"; + stmt_del = dbc->stmt[DB_STMT_AUC_3G_DELETE]; + stmt_ins = dbc->stmt[DB_STMT_AUC_3G_INSERT]; + switch (aud->algo) { + case OSMO_AUTH_ALG_NONE: + case OSMO_AUTH_ALG_MILENAGE: + break; + case OSMO_AUTH_ALG_COMP128v1: + case OSMO_AUTH_ALG_COMP128v2: + case OSMO_AUTH_ALG_COMP128v3: + case OSMO_AUTH_ALG_XOR: + LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:" + " auth algo not suited for 3G: %s\n", + osmo_auth_alg_name(aud->algo)); + return -EINVAL; + default: + LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:" + " Unknown auth algo: %d\n", aud->algo); + return -EINVAL; + } + + if (aud->algo == OSMO_AUTH_ALG_NONE) + break; + if (!osmo_is_hexstr(aud->u.umts.k, 32, 32, true)) { + LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:" + " Invalid K: '%s'\n", aud->u.umts.k); + return -EINVAL; + } + if (!osmo_is_hexstr(aud->u.umts.opc, 32, 32, true)) { + LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:" + " Invalid OP/OPC: '%s'\n", aud->u.umts.opc); + return -EINVAL; + } + if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX) { + LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:" + " Invalid ind_bitlen: %d\n", aud->u.umts.ind_bitlen); + return -EINVAL; + } + break; + default: + LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:" + " unknown auth type: %d\n", aud->type); + return -EINVAL; + } + + stmt = stmt_del; + + if (!db_bind_int64(stmt, "$subscriber_id", subscr_id)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + LOGP(DAUC, LOGL_ERROR, + "Cannot delete %s row: SQL error: (%d) %s\n", + label, rc, sqlite3_errmsg(dbc->db)); + ret = -EIO; + goto out; + } + + /* verify execution result */ + rc = sqlite3_changes(dbc->db); + if (!rc) + /* Leave "no such entry" logging to the caller -- during + * db_subscr_delete_by_id(), we call this to make sure it is + * empty, and no entry is not an error then.*/ + ret = -ENOENT; + else if (rc != 1) { + LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%"PRId64 + " from %s: SQL modified %d rows (expected 1)\n", + subscr_id, label, rc); + ret = -EIO; + } + + db_remove_reset(stmt); + + /* Error situation? Return now. */ + if (ret && ret != -ENOENT) + return ret; + + /* Just delete requested? */ + if (aud->algo == OSMO_AUTH_ALG_NONE) + return ret; + + /* Don't return -ENOENT if inserting new data. */ + ret = 0; + + /* Insert new row */ + stmt = stmt_ins; + + if (!db_bind_int64(stmt, "$subscriber_id", subscr_id)) + return -EIO; + + switch (aud->type) { + case OSMO_AUTH_TYPE_GSM: + if (!db_bind_int(stmt, "$algo_id_2g", aud->algo)) + return -EIO; + if (!db_bind_text(stmt, "$ki", aud->u.gsm.ki)) + return -EIO; + break; + case OSMO_AUTH_TYPE_UMTS: + if (!db_bind_int(stmt, "$algo_id_3g", aud->algo)) + return -EIO; + if (!db_bind_text(stmt, "$k", aud->u.umts.k)) + return -EIO; + if (!db_bind_text(stmt, "$op", + aud->u.umts.opc_is_op ? aud->u.umts.opc : NULL)) + return -EIO; + if (!db_bind_text(stmt, "$opc", + aud->u.umts.opc_is_op ? NULL : aud->u.umts.opc)) + return -EIO; + if (!db_bind_int(stmt, "$ind_bitlen", aud->u.umts.ind_bitlen)) + return -EIO; + break; + default: + OSMO_ASSERT(false); + } + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + LOGP(DAUC, LOGL_ERROR, + "Cannot insert %s row: SQL error: (%d) %s\n", + label, rc, sqlite3_errmsg(dbc->db)); + ret = -EIO; + goto out; + } + +out: + db_remove_reset(stmt); + return ret; +} + +/* Common code for db_subscr_get_by_*() functions. */ +static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscriber *subscr, + const char **err) +{ + int rc; + int ret = 0; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc == SQLITE_DONE) { + ret = -ENOENT; + goto out; + } + if (rc != SQLITE_ROW) { + ret = -EIO; + goto out; + } + + if (!subscr) + goto out; + + *subscr = (struct hlr_subscriber){}; + + /* obtain the various columns */ + subscr->id = sqlite3_column_int64(stmt, 0); + copy_sqlite3_text_to_buf(subscr->imsi, stmt, 1); + copy_sqlite3_text_to_buf(subscr->msisdn, stmt, 2); + /* FIXME: These should all be BLOBs as they might contain NUL */ + copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 3); + copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 4); + copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 5); + subscr->periodic_lu_timer = sqlite3_column_int(stmt, 6); + subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 7); + subscr->nam_cs = sqlite3_column_int(stmt, 8); + subscr->nam_ps = sqlite3_column_int(stmt, 9); + subscr->lmsi = sqlite3_column_int(stmt, 10); + subscr->ms_purged_cs = sqlite3_column_int(stmt, 11); + subscr->ms_purged_ps = sqlite3_column_int(stmt, 12); + +out: + db_remove_reset(stmt); + + switch (ret) { + case 0: + *err = NULL; + break; + case -ENOENT: + *err = "No such subscriber"; + break; + default: + *err = sqlite3_errmsg(dbc->db); + break; + } + return ret; +} + +/*! Retrieve subscriber data from the HLR database. + * \param[in,out] dbc database context. + * \param[in] imsi ASCII string of IMSI digits. + * \param[out] subscr place retrieved data in this struct. + * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on + * database error. + */ +int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi, + struct hlr_subscriber *subscr) +{ + sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMSI]; + const char *err; + int rc; + + if (!db_bind_text(stmt, NULL, imsi)) + return -EIO; + + rc = db_sel(dbc, stmt, subscr, &err); + if (rc) + LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMSI='%s': %s\n", + imsi, err); + return rc; +} + +/*! Retrieve subscriber data from the HLR database. + * \param[in,out] dbc database context. + * \param[in] msisdn ASCII string of MSISDN digits. + * \param[out] subscr place retrieved data in this struct. + * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on + * database error. + */ +int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn, + struct hlr_subscriber *subscr) +{ + sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_MSISDN]; + const char *err; + int rc; + + if (!db_bind_text(stmt, NULL, msisdn)) + return -EIO; + + rc = db_sel(dbc, stmt, subscr, &err); + if (rc) + LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: MSISDN='%s': %s\n", + msisdn, err); + return rc; +} + +/*! Retrieve subscriber data from the HLR database. + * \param[in,out] dbc database context. + * \param[in] id ID of the subscriber in the HLR db. + * \param[out] subscr place retrieved data in this struct. + * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on + * database error. + */ +int db_subscr_get_by_id(struct db_context *dbc, int64_t id, + struct hlr_subscriber *subscr) +{ + sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_ID]; + const char *err; + int rc; + + if (!db_bind_int64(stmt, NULL, id)) + return -EIO; + + rc = db_sel(dbc, stmt, subscr, &err); + if (rc) + LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%"PRId64": %s\n", + id, err); + return rc; +} + +/*! You should use hlr_subscr_nam() instead; enable or disable PS or CS for a + * subscriber without notifying GSUP clients. + * \param[in,out] dbc database context. + * \param[in] imsi ASCII string of IMSI digits. + * \param[in] nam_val True to enable CS/PS, false to disable. + * \param[in] is_ps when true, set nam_ps, else set nam_cs. + * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on + * database errors. + */ +int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps) +{ + sqlite3_stmt *stmt; + int rc; + int ret = 0; + + stmt = dbc->stmt[is_ps ? DB_STMT_UPD_NAM_PS_BY_IMSI + : DB_STMT_UPD_NAM_CS_BY_IMSI]; + + if (!db_bind_text(stmt, "$imsi", imsi)) + return -EIO; + if (!db_bind_int(stmt, "$val", nam_val ? 1 : 0)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL error: %s\n", + nam_val ? "enable" : "disable", + is_ps ? "PS" : "CS", + sqlite3_errmsg(dbc->db)); + ret = -EIO; + goto out; + } + + /* verify execution result */ + rc = sqlite3_changes(dbc->db); + if (!rc) { + LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n", + nam_val ? "enable" : "disable", + is_ps ? "PS" : "CS", + imsi); + ret = -ENOENT; + goto out; + } else if (rc != 1) { + LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n", + nam_val ? "enable" : "disable", + is_ps ? "PS" : "CS", + rc); + ret = -EIO; + } + +out: + db_remove_reset(stmt); + return ret; +} + +/*! Record a Location Updating in the database. + * \param[in,out] dbc database context. + * \param[in] subscr_id ID of the subscriber in the HLR db. + * \param[in] vlr_or_sgsn_number ASCII string of identifier digits. + * \param[in] is_ps when true, set sgsn_number, else set vlr_number. + * \returns 0 on success, -ENOENT when the given subscriber does not exist, + * -EIO on database errors. + */ +int db_subscr_lu(struct db_context *dbc, int64_t subscr_id, + const char *vlr_or_sgsn_number, bool is_ps) +{ + sqlite3_stmt *stmt; + int rc, ret = 0; + + stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID + : DB_STMT_UPD_VLR_BY_ID]; + + if (!db_bind_int64(stmt, "$subscriber_id", subscr_id)) + return -EIO; + + if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%"PRId64": SQL Error: %s\n", + is_ps? "SGSN" : "VLR", subscr_id, sqlite3_errmsg(dbc->db)); + ret = -EIO; + goto out; + } + + /* verify execution result */ + rc = sqlite3_changes(dbc->db); + if (!rc) { + LOGP(DAUC, LOGL_ERROR, "Cannot update %s number for subscriber ID=%"PRId64 + ": no such subscriber\n", + is_ps? "SGSN" : "VLR", subscr_id); + ret = -ENOENT; + } else if (rc != 1) { + LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%"PRId64 + ": SQL modified %d rows (expected 1)\n", + is_ps? "SGSN" : "VLR", subscr_id, rc); + ret = -EIO; + } + +out: + db_remove_reset(stmt); + return ret; +} + +/*! Set the ms_purged_cs or ms_purged_ps values in the database. + * \param[in,out] dbc database context. + * \param[in] by_imsi ASCII string of IMSI digits. + * \param[in] purge_val true to purge, false to un-purge. + * \param[in] is_ps when true, set ms_purged_ps, else set ms_purged_cs. + * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on + * database errors. + */ +int db_subscr_purge(struct db_context *dbc, const char *by_imsi, + bool purge_val, bool is_ps) +{ + sqlite3_stmt *stmt; + int rc, ret = 0; + + stmt = dbc->stmt[is_ps ? DB_STMT_UPD_PURGE_PS_BY_IMSI + : DB_STMT_UPD_PURGE_CS_BY_IMSI]; + + if (!db_bind_text(stmt, "$imsi", by_imsi)) + return -EIO; + if (!db_bind_int(stmt, "$val", purge_val ? 1 : 0)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + LOGP(DAUC, LOGL_ERROR, "%s %s: SQL error: %s\n", + purge_val ? "purge" : "un-purge", + is_ps ? "PS" : "CS", + sqlite3_errmsg(dbc->db)); + ret = -EIO; + goto out; + } + + /* verify execution result */ + rc = sqlite3_changes(dbc->db); + if (!rc) { + LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n", + purge_val ? "purge" : "un-purge", + is_ps ? "PS" : "CS", + by_imsi); + ret = -ENOENT; + goto out; + } else if (rc != 1) { + LOGHLR(by_imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n", + purge_val ? "purge" : "un-purge", + is_ps ? "PS" : "CS", + rc); + ret = -EIO; + } + +out: + db_remove_reset(stmt); + + return ret; +} + +/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients. + * \param[in,out] hlr Global hlr context. + * \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call. + * \param[in] nam_val True to enable CS/PS, false to disable. + * \param[in] is_ps True to enable/disable PS, false for CS. + * \returns 0 on success, ENOEXEC if there is no need to change, a negative + * value on error. + */ +int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps) +{ + int rc; + struct lu_operation *luop; + struct osmo_gsup_conn *co; + bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs; + + if (is_val == nam_val) { + LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n", + nam_val ? "enable" : "disable", is_ps ? "PS" : "CS"); + return ENOEXEC; + } + + rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps); + if (rc) + return rc > 0? -rc : rc; + + /* If we're disabling, send a notice out to the GSUP client that is + * responsible. Otherwise no need. */ + if (nam_val) + return 0; + + /* FIXME: only send to single SGSN where latest update for IMSI came from */ + llist_for_each_entry(co, &hlr->gs->clients, list) { + luop = lu_op_alloc_conn(co); + if (!luop) { + LOGHLR(subscr->imsi, LOGL_ERROR, + "Cannot notify GSUP client, cannot allocate lu_operation," + " for %s:%u\n", + co && co->conn && co->conn->server? co->conn->server->addr : "unset", + co && co->conn && co->conn->server? co->conn->server->port : 0); + continue; + } + luop->subscr = *subscr; + lu_op_tx_del_subscr_data(luop); + lu_op_free(luop); + } + return 0; +} diff --git a/src/db_test.c b/src/db_test.c new file mode 100644 index 0000000..0e823f9 --- /dev/null +++ b/src/db_test.c @@ -0,0 +1,87 @@ +#include + +#include +#include + +#include "db.h" +#include "hlr.h" +#include "rand.h" +#include "logging.h" + +static struct hlr *g_hlr; + +static int test(const char *imsi, struct db_context *dbc) +{ + struct osmo_auth_vector vec[3]; + int rc, i; + + /* initialize all vectors with a known token pattern */ + memset(vec, 0x55, sizeof(vec)); + for (i = 0; i < ARRAY_SIZE(vec); i++) + vec[i].res_len = 0; + + rc = db_get_auc(dbc, imsi, 0, vec, ARRAY_SIZE(vec), NULL, NULL); + if (rc <= 0) { + LOGP(DMAIN, LOGL_ERROR, "Cannot obtain auth tuples for '%s'\n", imsi); + return rc; + } + LOGP(DMAIN, LOGL_INFO, "Obtained %u tuples for subscriber IMSI %s\n", + rc, imsi); + + for (i = 0; i < rc; i++) { + struct osmo_auth_vector *v = vec + i; + LOGP(DMAIN, LOGL_DEBUG, "Tuple %u, auth_types=0x%x\n", i, v->auth_types); + LOGP(DMAIN, LOGL_DEBUG, "RAND=%s\n", osmo_hexdump_nospc(v->rand, sizeof(v->rand))); + LOGP(DMAIN, LOGL_DEBUG, "AUTN=%s\n", osmo_hexdump_nospc(v->autn, sizeof(v->autn))); + LOGP(DMAIN, LOGL_DEBUG, "CK=%s\n", osmo_hexdump_nospc(v->ck, sizeof(v->ck))); + LOGP(DMAIN, LOGL_DEBUG, "IK=%s\n", osmo_hexdump_nospc(v->ik, sizeof(v->ik))); + LOGP(DMAIN, LOGL_DEBUG, "RES=%s\n", osmo_hexdump_nospc(v->res, v->res_len)); + LOGP(DMAIN, LOGL_DEBUG, "Kc=%s\n", osmo_hexdump_nospc(v->kc, sizeof(v->kc))); + LOGP(DMAIN, LOGL_DEBUG, "SRES=%s\n", osmo_hexdump_nospc(v->sres, sizeof(v->sres))); + } + + return rc; +} + +int main(int argc, char **argv) +{ + int rc; + + g_hlr = talloc_zero(NULL, struct hlr); + + rc = osmo_init_logging(&hlr_log_info); + if (rc < 0) { + fprintf(stderr, "Error initializing logging\n"); + exit(1); + } + LOGP(DMAIN, LOGL_NOTICE, "hlr starting\n"); + + rc = rand_init(); + if (rc < 0) { + LOGP(DMAIN, LOGL_ERROR, "Error initializing random source\n"); + exit(1); + } + + g_hlr->dbc = db_open(NULL, "hlr.db"); + if (!g_hlr->dbc) { + LOGP(DMAIN, LOGL_ERROR, "Error opening database\n"); + exit(1); + } + + /* non-existing subscriber */ + rc = test("901990123456789", g_hlr->dbc); + /* 2G only AUC data (COMP128v1 / MILENAGE) */ + rc = test("901990000000001", g_hlr->dbc); + /* 2G + 3G AUC data (COMP128v1 / MILENAGE) */ + rc = test("901990000000002", g_hlr->dbc); + /* 3G AUC data (MILENAGE) */ + rc = test("901990000000003", g_hlr->dbc); + + LOGP(DMAIN, LOGL_NOTICE, "Exiting\n"); + + db_close(g_hlr->dbc); + + log_fini(); + + exit(0); +} diff --git a/src/dbd_decode_binary.c b/src/dbd_decode_binary.c new file mode 100644 index 0000000..e1a98ad --- /dev/null +++ b/src/dbd_decode_binary.c @@ -0,0 +1,42 @@ +/* This function is blatantly copied from libdbi, from + * https://sourceforge.net/p/libdbi/libdbi/ci/master/tree/src/dbd_helper.c + * to save having to depend on the entire libdbi just for KI BLOB decoding. + */ + +/* + * libdbi - database independent abstraction layer for C. + * Copyright (C) 2001-2003, David Parker and Mark Tobenkin. + * http://libdbi.sourceforge.net + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id: dbd_helper.c,v 1.44 2011/08/09 11:14:14 mhoenicka Exp $ + */ + +#include + +size_t _dbd_decode_binary(const unsigned char *in, unsigned char *out){ + int i, e; + unsigned char c; + e = *(in++); + i = 0; + while( (c = *(in++))!=0 ){ + if( c==1 ){ + c = *(in++) - 1; + } + out[i++] = c + e; + } + return (size_t)i; +} diff --git a/src/gsup_router.c b/src/gsup_router.c new file mode 100644 index 0000000..e9aed78 --- /dev/null +++ b/src/gsup_router.c @@ -0,0 +1,85 @@ +/* (C) 2016 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include + +#include +#include + +#include "gsup_server.h" + +struct gsup_route { + struct llist_head list; + + uint8_t *addr; + struct osmo_gsup_conn *conn; +}; + +/* find a route for the given address */ +struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs, + const uint8_t *addr, size_t addrlen) +{ + struct gsup_route *gr; + + llist_for_each_entry(gr, &gs->routes, list) { + if (talloc_total_size(gr->addr) == addrlen && + !memcmp(gr->addr, addr, addrlen)) + return gr->conn; + } + return NULL; +} + +/* add a new route for the given address to the given conn */ +int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen) +{ + struct gsup_route *gr; + + /* Check if we already have a route for this address */ + if (gsup_route_find(conn->server, addr, addrlen)) + return -EEXIST; + + /* allocate new route and populate it */ + gr = talloc_zero(conn->server, struct gsup_route); + if (!gr) + return -ENOMEM; + + gr->addr = talloc_memdup(gr, addr, addrlen); + gr->conn = conn; + llist_add_tail(&gr->list, &conn->server->routes); + + return 0; +} + +/* delete all routes for the given connection */ +int gsup_route_del_conn(struct osmo_gsup_conn *conn) +{ + struct gsup_route *gr, *gr2; + unsigned int num_deleted = 0; + + llist_for_each_entry_safe(gr, gr2, &conn->server->routes, list) { + if (gr->conn == conn) { + llist_del(&gr->list); + talloc_free(gr); + num_deleted++; + } + } + + return num_deleted; +} diff --git a/src/gsup_router.h b/src/gsup_router.h new file mode 100644 index 0000000..7a5bd25 --- /dev/null +++ b/src/gsup_router.h @@ -0,0 +1,8 @@ +struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs, + const uint8_t *addr, size_t addrlen); + +/* add a new route for the given address to the given conn */ +int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen); + +/* delete all routes for the given connection */ +int gsup_route_del_conn(struct osmo_gsup_conn *conn); diff --git a/src/gsup_server.c b/src/gsup_server.c new file mode 100644 index 0000000..b382c86 --- /dev/null +++ b/src/gsup_server.c @@ -0,0 +1,335 @@ +/* (C) 2016 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include +#include +#include +#include + +#include "gsup_server.h" +#include "gsup_router.h" + +static void osmo_gsup_server_send(struct osmo_gsup_conn *conn, + int proto_ext, struct msgb *msg_tx) +{ + ipa_prepend_header_ext(msg_tx, proto_ext); + ipa_msg_push_header(msg_tx, IPAC_PROTO_OSMO); + ipa_server_conn_send(conn->conn, msg_tx); +} + +int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg) +{ + if (!conn) { + msgb_free(msg); + return -ENOTCONN; + } + + osmo_gsup_server_send(conn, IPAC_PROTO_EXT_GSUP, msg); + + return 0; +} + +static int osmo_gsup_conn_oap_handle(struct osmo_gsup_conn *conn, + struct msgb *msg_rx) +{ +#if 0 + int rc; + struct msgb *msg_tx; + rc = oap_handle(&conn->oap_state, msg_rx, &msg_tx); + msgb_free(msg_rx); + if (rc < 0) + return rc; + + if (msg_tx) + osmo_gsup_conn_send(conn, IPAC_PROTO_EXT_OAP, msg_tx); +#endif + return 0; +} + +/* Data from a given client has arrived over the socket */ +static int osmo_gsup_server_read_cb(struct ipa_server_conn *conn, + struct msgb *msg) +{ + struct ipaccess_head *hh = (struct ipaccess_head *) msg->data; + struct ipaccess_head_ext *he = (struct ipaccess_head_ext *) msgb_l2(msg); + struct osmo_gsup_conn *clnt = (struct osmo_gsup_conn *)conn->data; + int rc; + + msg->l2h = &hh->data[0]; + + if (hh->proto == IPAC_PROTO_IPACCESS) { + rc = ipa_server_conn_ccm(conn, msg); + if (rc < 0) { + /* conn is already invalid here! */ + return -1; + } + msgb_free(msg); + return 0; + } + + if (hh->proto != IPAC_PROTO_OSMO) { + LOGP(DLGSUP, LOGL_NOTICE, "Unsupported IPA stream ID 0x%02x\n", + hh->proto); + goto invalid; + } + + if (!he || msgb_l2len(msg) < sizeof(*he)) { + LOGP(DLGSUP, LOGL_NOTICE, "short IPA message\n"); + goto invalid; + } + + msg->l2h = &he->data[0]; + + if (he->proto == IPAC_PROTO_EXT_GSUP) { + OSMO_ASSERT(clnt->server->read_cb != NULL); + clnt->server->read_cb(clnt, msg); + /* expecting read_cb() to free msg */ + } else if (he->proto == IPAC_PROTO_EXT_OAP) { + return osmo_gsup_conn_oap_handle(clnt, msg); + /* osmo_gsup_client_oap_handle frees msg */ + } else { + LOGP(DLGSUP, LOGL_NOTICE, "Unsupported IPA Osmo Proto 0x%02x\n", + hh->proto); + goto invalid; + } + + return 0; + +invalid: + LOGP(DLGSUP, LOGL_NOTICE, + "GSUP received an invalid IPA message from %s:%d: %s\n", + conn->addr, conn->port, osmo_hexdump(msgb_l2(msg), msgb_l2len(msg))); + msgb_free(msg); + return -1; + +} + +static void osmo_tlvp_dump(const struct tlv_parsed *tlvp, + int subsys, int level) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tlvp->lv); i++) { + if (!TLVP_PRESENT(tlvp, i)) + continue; + + LOGP(subsys, level, "%u: %s\n", i, + TLVP_VAL(tlvp, i)); + LOGP(subsys, level, "%u: %s\n", i, + osmo_hexdump(TLVP_VAL(tlvp, i), + TLVP_LEN(tlvp, i))); + } +} + +/* FIXME: should this be parrt of ipas_server handling, not GSUP? */ +static void tlvp_copy(void *ctx, struct tlv_parsed *out, const struct tlv_parsed *in) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(out->lv); i++) { + if (!TLVP_PRESENT(in, i)) { + if (TLVP_PRESENT(out, i)) { + talloc_free((void *) out->lv[i].val); + out->lv[i].val = NULL; + out->lv[i].len = 0; + } + continue; + } + out->lv[i].val = talloc_memdup(ctx, in->lv[i].val, in->lv[i].len); + out->lv[i].len = in->lv[i].len; + } +} + +int osmo_gsup_conn_ccm_get(const struct osmo_gsup_conn *clnt, uint8_t **addr, + uint8_t tag) +{ + if (!TLVP_PRESENT(&clnt->ccm, tag)) + return -ENODEV; + *addr = (uint8_t *) TLVP_VAL(&clnt->ccm, tag); + + return TLVP_LEN(&clnt->ccm, tag); +} + +static int osmo_gsup_server_ccm_cb(struct ipa_server_conn *conn, + struct msgb *msg, struct tlv_parsed *tlvp, + struct ipaccess_unit *unit) +{ + struct osmo_gsup_conn *clnt = (struct osmo_gsup_conn *)conn->data; + uint8_t *addr = NULL; + size_t addr_len; + + LOGP(DLGSUP, LOGL_INFO, "CCM Callback\n"); + + /* FIXME: should this be parrt of ipas_server handling, not + * GSUP? */ + tlvp_copy(clnt, &clnt->ccm, tlvp); + osmo_tlvp_dump(tlvp, DLGSUP, LOGL_INFO); + + addr_len = osmo_gsup_conn_ccm_get(clnt, &addr, IPAC_IDTAG_SERNR); + if (addr_len <= 0) { + LOGP(DLGSUP, LOGL_ERROR, "GSUP client %s:%u has no %s IE and" + " cannot be routed\n", + conn->addr, conn->port, + ipa_ccm_idtag_name(IPAC_IDTAG_SERNR)); + return -EINVAL; + } + + gsup_route_add(clnt, addr, addr_len); + return 0; +} + +static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn) +{ + struct osmo_gsup_conn *clnt = (struct osmo_gsup_conn *)conn->data; + + LOGP(DLGSUP, LOGL_INFO, "Lost GSUP client %s:%d\n", + conn->addr, conn->port); + + gsup_route_del_conn(clnt); + llist_del(&clnt->list); + talloc_free(clnt); + + return 0; +} + +/* Add conn to the clients list in a way that conn->auc_3g_ind takes the lowest + * unused integer and the list of clients remains sorted by auc_3g_ind. + * Keep this function non-static to allow linking in a unit test. */ +void osmo_gsup_server_add_conn(struct llist_head *clients, + struct osmo_gsup_conn *conn) +{ + struct osmo_gsup_conn *c; + struct osmo_gsup_conn *prev_conn; + + c = llist_first_entry_or_null(clients, struct osmo_gsup_conn, list); + + /* Is the first index, 0, unused? */ + if (!c || c->auc_3g_ind > 0) { + conn->auc_3g_ind = 0; + llist_add(&conn->list, clients); + return; + } + + /* Look for a gap later on */ + prev_conn = NULL; + llist_for_each_entry(c, clients, list) { + /* skip first item, we know it has auc_3g_ind == 0. */ + if (!prev_conn) { + prev_conn = c; + continue; + } + if (c->auc_3g_ind > prev_conn->auc_3g_ind + 1) + break; + prev_conn = c; + } + + OSMO_ASSERT(prev_conn); + + conn->auc_3g_ind = prev_conn->auc_3g_ind + 1; + llist_add(&conn->list, &prev_conn->list); +} + +/* a client has connected to the server socket and we have accept()ed it */ +static int osmo_gsup_server_accept_cb(struct ipa_server_link *link, int fd) +{ + struct osmo_gsup_conn *conn; + struct osmo_gsup_server *gsups = + (struct osmo_gsup_server *) link->data; + int rc; + + conn = talloc_zero(gsups, struct osmo_gsup_conn); + OSMO_ASSERT(conn); + + conn->conn = ipa_server_conn_create(gsups, link, fd, + osmo_gsup_server_read_cb, + osmo_gsup_server_closed_cb, conn); + OSMO_ASSERT(conn->conn); + conn->conn->ccm_cb = osmo_gsup_server_ccm_cb; + + /* link data structure with server structure */ + conn->server = gsups; + osmo_gsup_server_add_conn(&gsups->clients, conn); + + LOGP(DLGSUP, LOGL_INFO, "New GSUP client %s:%d (IND=%u)\n", + conn->conn->addr, conn->conn->port, conn->auc_3g_ind); + + /* request the identity of the client */ + rc = ipa_ccm_send_id_req(fd); + if (rc < 0) + goto failed; +#if 0 + rc = oap_init(&gsups->oap_config, &conn->oap_state); + if (rc != 0) + goto failed; +#endif + return 0; +failed: + ipa_server_conn_destroy(conn->conn); + return -1; +} + +struct osmo_gsup_server * +osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port, + osmo_gsup_read_cb_t read_cb, + struct llist_head *lu_op_lst) +{ + struct osmo_gsup_server *gsups; + int rc; + + gsups = talloc_zero(ctx, struct osmo_gsup_server); + OSMO_ASSERT(gsups); + + INIT_LLIST_HEAD(&gsups->clients); + INIT_LLIST_HEAD(&gsups->routes); + + gsups->link = ipa_server_link_create(gsups, + /* no e1inp */ NULL, + ip_addr, tcp_port, + osmo_gsup_server_accept_cb, + gsups); + if (!gsups->link) + goto failed; + + gsups->read_cb = read_cb; + + rc = ipa_server_link_open(gsups->link); + if (rc < 0) + goto failed; + + gsups->luop = lu_op_lst; + + return gsups; + +failed: + osmo_gsup_server_destroy(gsups); + return NULL; +} + +void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups) +{ + if (gsups->link) { + ipa_server_link_close(gsups->link); + ipa_server_link_destroy(gsups->link); + gsups->link = NULL; + } + talloc_free(gsups); +} diff --git a/src/gsup_server.h b/src/gsup_server.h new file mode 100644 index 0000000..74062d4 --- /dev/null +++ b/src/gsup_server.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +struct osmo_gsup_conn; + +/* Expects message in msg->l2h */ +typedef int (*osmo_gsup_read_cb_t)(struct osmo_gsup_conn *conn, struct msgb *msg); + +struct osmo_gsup_server { + /* list of osmo_gsup_conn */ + struct llist_head clients; + + /* lu_operations list */ + struct llist_head *luop; + + struct ipa_server_link *link; + osmo_gsup_read_cb_t read_cb; + struct llist_head routes; +}; + + +/* a single connection to a given client (SGSN, MSC) */ +struct osmo_gsup_conn { + struct llist_head list; + + struct osmo_gsup_server *server; + struct ipa_server_conn *conn; + //struct oap_state oap_state; + struct tlv_parsed ccm; + + unsigned int auc_3g_ind; /*!< IND index used for UMTS AKA SQN */ +}; + + +int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg); +int osmo_gsup_conn_ccm_get(const struct osmo_gsup_conn *clnt, uint8_t **addr, + uint8_t tag); + +struct osmo_gsup_server *osmo_gsup_server_create(void *ctx, + const char *ip_addr, + uint16_t tcp_port, + osmo_gsup_read_cb_t read_cb, + struct llist_head *lu_op_lst); + +void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups); + diff --git a/src/hlr.c b/src/hlr.c new file mode 100644 index 0000000..6310526 --- /dev/null +++ b/src/hlr.c @@ -0,0 +1,500 @@ +/* (C) 2016 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "db.h" +#include "hlr.h" +#include "ctrl.h" +#include "logging.h" +#include "gsup_server.h" +#include "gsup_router.h" +#include "rand.h" +#include "luop.h" +#include "hlr_vty.h" + +static struct hlr *g_hlr; + +/*********************************************************************** + * Send Auth Info handling + ***********************************************************************/ + +/* process an incoming SAI request */ +static int rx_send_auth_info(struct osmo_gsup_conn *conn, + const struct osmo_gsup_message *gsup, + struct db_context *dbc) +{ + struct osmo_gsup_message gsup_out; + struct msgb *msg_out; + int rc; + + /* initialize return message structure */ + memset(&gsup_out, 0, sizeof(gsup_out)); + memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi)); + + rc = db_get_auc(dbc, gsup->imsi, conn->auc_3g_ind, + gsup_out.auth_vectors, + ARRAY_SIZE(gsup_out.auth_vectors), + gsup->rand, gsup->auts); + if (rc < 0) { + gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR; + gsup_out.cause = GMM_CAUSE_NET_FAIL; + } else if (rc == 0) { + gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR; + gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN; + } else { + gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT; + gsup_out.num_auth_vectors = rc; + } + + msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response"); + osmo_gsup_encode(msg_out, &gsup_out); + return osmo_gsup_conn_send(conn, msg_out); +} + +/*********************************************************************** + * LU Operation State / Structure + ***********************************************************************/ + +static LLIST_HEAD(g_lu_ops); + +/*! Receive Cancel Location Result from old VLR/SGSN */ +void lu_op_rx_cancel_old_ack(struct lu_operation *luop, + const struct osmo_gsup_message *gsup) +{ + OSMO_ASSERT(luop->state == LU_S_CANCEL_SENT); + /* FIXME: Check for spoofing */ + + osmo_timer_del(&luop->timer); + + /* FIXME */ + + lu_op_tx_insert_subscr_data(luop); +} + +/*! Receive Insert Subscriber Data Result from new VLR/SGSN */ +static void lu_op_rx_insert_subscr_data_ack(struct lu_operation *luop, + const struct osmo_gsup_message *gsup) +{ + OSMO_ASSERT(luop->state == LU_S_ISD_SENT); + /* FIXME: Check for spoofing */ + + osmo_timer_del(&luop->timer); + + /* Subscriber_Present_HLR */ + /* CS only: Check_SS_required? -> MAP-FW-CHECK_SS_IND.req */ + + /* Send final ACK towards inquiring VLR/SGSN */ + lu_op_tx_ack(luop); +} + +/*! Receive GSUP message for given \ref lu_operation */ +void lu_op_rx_gsup(struct lu_operation *luop, + const struct osmo_gsup_message *gsup) +{ + switch (gsup->message_type) { + case OSMO_GSUP_MSGT_INSERT_DATA_ERROR: + /* FIXME */ + break; + case OSMO_GSUP_MSGT_INSERT_DATA_RESULT: + lu_op_rx_insert_subscr_data_ack(luop, gsup); + break; + case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR: + /* FIXME */ + break; + case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT: + lu_op_rx_cancel_old_ack(luop, gsup); + break; + default: + LOGP(DMAIN, LOGL_ERROR, "Unhandled GSUP msg_type 0x%02x\n", + gsup->message_type); + break; + } +} + +/*! Receive Update Location Request, creates new \ref lu_operation */ +static int rx_upd_loc_req(struct osmo_gsup_conn *conn, + const struct osmo_gsup_message *gsup) +{ + struct lu_operation *luop = lu_op_alloc_conn(conn); + if (!luop) { + LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n"); + return -EINVAL; + } + + lu_op_statechg(luop, LU_S_LU_RECEIVED); + + if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS) + luop->is_ps = true; + llist_add(&luop->list, &g_lu_ops); + + /* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */ + + /* check if subscriber is known at all */ + if (!lu_op_fill_subscr(luop, g_hlr->dbc, gsup->imsi)) { + /* Send Error back: Subscriber Unknown in HLR */ + strcpy(luop->subscr.imsi, gsup->imsi); + lu_op_tx_error(luop, GMM_CAUSE_IMSI_UNKNOWN); + return 0; + } + + /* Check if subscriber is generally permitted on CS or PS + * service (as requested) */ + if (!luop->is_ps && !luop->subscr.nam_cs) { + lu_op_tx_error(luop, GMM_CAUSE_PLMN_NOTALLOWED); + return 0; + } else if (luop->is_ps && !luop->subscr.nam_ps) { + lu_op_tx_error(luop, GMM_CAUSE_GPRS_NOTALLOWED); + return 0; + } + + /* TODO: Set subscriber tracing = deactive in VLR/SGSN */ + +#if 0 + /* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old */ + if (luop->is_ps == false && + strcmp(subscr->vlr_number, vlr_number)) { + lu_op_tx_cancel_old(luop); + } else if (luop->is_ps == true && + strcmp(subscr->sgsn_number, sgsn_number)) { + lu_op_tx_cancel_old(luop); + } else +#endif + { + /* TODO: Subscriber allowed to roam in PLMN? */ + /* TODO: Update RoutingInfo */ + /* TODO: Reset Flag MS Purged (cs/ps) */ + /* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */ + lu_op_tx_insert_subscr_data(luop); + } + return 0; +} + +static int rx_purge_ms_req(struct osmo_gsup_conn *conn, + const struct osmo_gsup_message *gsup) +{ + struct osmo_gsup_message gsup_reply = {0}; + struct msgb *msg_out; + bool is_ps = false; + int rc; + + LOGP(DAUC, LOGL_INFO, "%s: Purge MS (%s)\n", gsup->imsi, + is_ps ? "PS" : "CS"); + + memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi)); + + if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS) + is_ps = true; + + /* FIXME: check if the VLR that sends the purge is the same that + * we have on record. Only update if yes */ + + /* Perform the actual update of the DB */ + rc = db_subscr_purge(g_hlr->dbc, gsup->imsi, true, is_ps); + + if (rc == 1) + gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_RESULT; + else if (rc == 0) { + gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR; + gsup_reply.cause = GMM_CAUSE_IMSI_UNKNOWN; + } else { + gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR; + gsup_reply.cause = GMM_CAUSE_NET_FAIL; + } + + msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response"); + osmo_gsup_encode(msg_out, &gsup_reply); + return osmo_gsup_conn_send(conn, msg_out); +} + +static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg) +{ + static struct osmo_gsup_message gsup; + int rc; + + rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup); + if (rc < 0) { + LOGP(DMAIN, LOGL_ERROR, "error in GSUP decode: %d\n", rc); + return rc; + } + + switch (gsup.message_type) { + /* requests sent to us */ + case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST: + rx_send_auth_info(conn, &gsup, g_hlr->dbc); + break; + case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST: + rx_upd_loc_req(conn, &gsup); + break; + case OSMO_GSUP_MSGT_PURGE_MS_REQUEST: + rx_purge_ms_req(conn, &gsup); + break; + /* responses to requests sent by us */ + case OSMO_GSUP_MSGT_DELETE_DATA_ERROR: + LOGP(DMAIN, LOGL_ERROR, "Error while deleting subscriber data " + "for IMSI %s\n", gsup.imsi); + break; + case OSMO_GSUP_MSGT_DELETE_DATA_RESULT: + LOGP(DMAIN, LOGL_ERROR, "Deleting subscriber data for IMSI %s\n", + gsup.imsi); + break; + case OSMO_GSUP_MSGT_INSERT_DATA_ERROR: + case OSMO_GSUP_MSGT_INSERT_DATA_RESULT: + case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR: + case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT: + { + struct lu_operation *luop = lu_op_by_imsi(gsup.imsi, + &g_lu_ops); + if (!luop) { + LOGP(DMAIN, LOGL_ERROR, "GSUP message %s for " + "unknown IMSI %s\n", + osmo_gsup_message_type_name(gsup.message_type), + gsup.imsi); + break; + } + lu_op_rx_gsup(luop, &gsup); + } + break; + default: + LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n", + osmo_gsup_message_type_name(gsup.message_type)); + break; + } + msgb_free(msg); + return 0; +} + +static void print_usage() +{ + printf("Usage: osmo-hlr\n"); +} + +static void print_help() +{ + printf(" -h --help This text.\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -l --database db-name The database to use.\n"); + printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM Enable debugging.\n"); + printf(" -D --daemonize Fork the process into a background daemon.\n"); + printf(" -s --disable-color Do not print ANSI colors in the log\n"); + printf(" -T --timestamp Prefix every log line with a timestamp.\n"); + printf(" -e --log-level number Set a global loglevel.\n"); + printf(" -V --version Print the version of OsmoHLR.\n"); +} + +static struct { + const char *config_file; + const char *db_file; + bool daemonize; +} cmdline_opts = { + .config_file = "osmo-hlr.cfg", + .db_file = "hlr.db", + .daemonize = false, +}; + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"config-file", 1, 0, 'c'}, + {"database", 1, 0, 'l'}, + {"debug", 1, 0, 'd'}, + {"daemonize", 0, 0, 'D'}, + {"disable-color", 0, 0, 's'}, + {"log-level", 1, 0, 'e'}, + {"timestamp", 0, 0, 'T'}, + {"version", 0, 0, 'V' }, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hc:l:d:Dse:TV", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 'c': + cmdline_opts.config_file = optarg; + break; + case 'l': + cmdline_opts.db_file = optarg; + break; + case 'd': + log_parse_category_mask(osmo_stderr_target, optarg); + break; + case 'D': + cmdline_opts.daemonize = 1; + break; + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 'T': + log_set_print_timestamp(osmo_stderr_target, 1); + break; + case 'V': + print_version(1); + exit(0); + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + exit(-1); + break; + } + } +} + +static void *hlr_ctx = NULL; + +static void signal_hdlr(int signal) +{ + switch (signal) { + case SIGINT: + LOGP(DMAIN, LOGL_NOTICE, "Terminating due to SIGINT\n"); + osmo_gsup_server_destroy(g_hlr->gs); + db_close(g_hlr->dbc); + log_fini(); + talloc_report_full(hlr_ctx, stderr); + exit(0); + break; + case SIGUSR1: + LOGP(DMAIN, LOGL_DEBUG, "Talloc Report due to SIGUSR1\n"); + talloc_report_full(hlr_ctx, stderr); + break; + } +} + +static const char vlr_copyright[] = + "Copyright (C) 2016, 2017 by Harald Welte, sysmocom s.f.m.c. GmbH\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static struct vty_app_info vty_info = { + .name = "OsmoHLR", + .version = PACKAGE_VERSION, + .copyright = vlr_copyright, + .is_config_node = hlr_vty_is_config_node, + .go_parent_cb = hlr_vty_go_parent, +}; + +int main(int argc, char **argv) +{ + int rc; + + hlr_ctx = talloc_named_const(NULL, 1, "OsmoHLR"); + msgb_talloc_ctx_init(hlr_ctx, 0); + + g_hlr = talloc_zero(hlr_ctx, struct hlr); + + rc = osmo_init_logging(&hlr_log_info); + if (rc < 0) { + fprintf(stderr, "Error initializing logging\n"); + exit(1); + } + + vty_init(&vty_info); + ctrl_vty_init(hlr_ctx); + handle_options(argc, argv); + hlr_vty_init(g_hlr, &hlr_log_info); + + rc = vty_read_config_file(cmdline_opts.config_file, NULL); + if (rc < 0) { + LOGP(DMAIN, LOGL_FATAL, + "Failed to parse the config file: '%s'\n", + cmdline_opts.config_file); + return rc; + } + + /* start telnet after reading config for vty_get_bind_addr() */ + rc = telnet_init_dynif(hlr_ctx, NULL, vty_get_bind_addr(), + OSMO_VTY_PORT_HLR); + if (rc < 0) + return rc; + + LOGP(DMAIN, LOGL_NOTICE, "hlr starting\n"); + + rc = rand_init(); + if (rc < 0) { + LOGP(DMAIN, LOGL_FATAL, "Error initializing random source\n"); + exit(1); + } + + g_hlr->dbc = db_open(hlr_ctx, cmdline_opts.db_file); + if (!g_hlr->dbc) { + LOGP(DMAIN, LOGL_FATAL, "Error opening database\n"); + exit(1); + } + + g_hlr->gs = osmo_gsup_server_create(hlr_ctx, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT, + read_cb, &g_lu_ops); + if (!g_hlr->gs) { + LOGP(DMAIN, LOGL_FATAL, "Error starting GSUP server\n"); + exit(1); + } + + g_hlr->ctrl_bind_addr = ctrl_vty_get_bind_addr(); + g_hlr->ctrl = hlr_controlif_setup(g_hlr); + + osmo_init_ignore_signals(); + signal(SIGINT, &signal_hdlr); + signal(SIGUSR1, &signal_hdlr); + + if (cmdline_opts.daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + osmo_select_main(0); + } + + db_close(g_hlr->dbc); + + log_fini(); + + exit(0); +} diff --git a/src/hlr.h b/src/hlr.h new file mode 100644 index 0000000..f63bc2b --- /dev/null +++ b/src/hlr.h @@ -0,0 +1,40 @@ +/* OsmoHLR generic header */ + +/* (C) 2017 sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Max Suraev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include + +struct hlr { + /* GSUP server pointer */ + struct osmo_gsup_server *gs; + + /* DB context */ + struct db_context *dbc; + + /* Control Interface */ + struct ctrl_handle *ctrl; + const char *ctrl_bind_addr; + + /* Local bind addr */ + char *gsup_bind_addr; +}; diff --git a/src/hlr_db_tool.c b/src/hlr_db_tool.c new file mode 100644 index 0000000..8982739 --- /dev/null +++ b/src/hlr_db_tool.c @@ -0,0 +1,426 @@ +/* (C) 2017 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "logging.h" +#include "db.h" +#include "rand.h" + +struct hlr_db_tool_ctx { + /* DB context */ + struct db_context *dbc; +}; + +struct hlr_db_tool_ctx *g_hlr_db_tool_ctx; + +static struct { + const char *db_file; + bool bootstrap; + const char *import_nitb_db; +} cmdline_opts = { + .db_file = "hlr.db", +}; + +static void print_help() +{ + printf("\n"); + printf("Usage: osmo-hlr-db-tool [-l ] import-nitb-db ]\n"); + printf("Call without arguments to create a new empty ./hlr.db.\n"); + printf(" -l --database db-name The OsmoHLR database to use, default '%s'.\n", + cmdline_opts.db_file); + printf(" -h --help This text.\n"); + printf(" -d option --debug=DMAIN:DDB:DAUC Enable debugging.\n"); + printf(" -s --disable-color Do not print ANSI colors in the log\n"); + printf(" -T --timestamp Prefix every log line with a timestamp.\n"); + printf(" -e --log-level number Set a global loglevel.\n"); + printf(" -V --version Print the version of OsmoHLR-db-tool.\n"); + printf("\n"); + printf(" import-nitb-db db Add OsmoNITB db's subscribers to OsmoHLR db.\n"); + printf(" Be aware that the import is lossy, only the\n"); + printf(" IMSI, MSISDN, nam_cs/ps and 2G auth data are set.\n"); +} + +static void print_version(int print_copyright) +{ + printf("OsmoHLR-db-tool version %s\n", PACKAGE_VERSION); + if (print_copyright) + printf("\n" + "Copyright (C) 2017 by sysmocom - s.f.m.c. GmbH\n" + "License AGPLv3+: GNU AGPL version 3 or later \n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n" + "\n"); +} + +static void handle_options(int argc, char **argv) +{ + const char *cmd; + + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"database", 1, 0, 'l'}, + {"debug", 1, 0, 'd'}, + {"disable-color", 0, 0, 's'}, + {"timestamp", 0, 0, 'T'}, + {"log-level", 1, 0, 'e'}, + {"version", 0, 0, 'V' }, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hl:d:sTe:V", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(EXIT_SUCCESS); + case 'l': + cmdline_opts.db_file = optarg; + break; + case 'd': + log_parse_category_mask(osmo_stderr_target, optarg); + break; + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'T': + log_set_print_timestamp(osmo_stderr_target, 1); + break; + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 'V': + print_version(1); + exit(EXIT_SUCCESS); + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + exit(EXIT_FAILURE); + break; + } + } + + if (argc - optind <= 0) { + fprintf(stderr, "Error: You must specify a command.\n"); + print_help(); + exit(EXIT_FAILURE); + } + + cmd = argv[optind++]; + printf("command '%s', %d extra arguments\n", cmd, argc - optind); + + if (!strcmp(cmd, "import-nitb-db")) { + if (argc - optind < 1) { + fprintf(stderr, "You must specify an input db file\n"); + print_help(); + exit(EXIT_FAILURE); + } + cmdline_opts.import_nitb_db = argv[optind++]; + } else { + fprintf(stderr, "Error: Unknown command `%s'\n", cmd); + print_help(); + exit(EXIT_FAILURE); + } +} + +static void signal_hdlr(int signal) +{ + switch (signal) { + case SIGINT: + LOGP(DMAIN, LOGL_NOTICE, "Terminating due to SIGINT\n"); + db_close(g_hlr_db_tool_ctx->dbc); + log_fini(); + talloc_report_full(g_hlr_db_tool_ctx, stderr); + exit(EXIT_SUCCESS); + break; + case SIGUSR1: + LOGP(DMAIN, LOGL_DEBUG, "Talloc Report due to SIGUSR1\n"); + talloc_report_full(g_hlr_db_tool_ctx, stderr); + break; + } +} + +sqlite3 *open_nitb_db(const char *filename) +{ + int rc; + sqlite3 *nitb_db = NULL; + + rc = sqlite3_open(filename, &nitb_db); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to open OsmoNITB DB %s; rc = %d\n", filename, rc); + return NULL; + } + + return nitb_db; +} + +enum nitb_stmt { + NITB_SELECT_SUBSCR, + NITB_SELECT_AUTH_KEYS, +}; + +static const char *nitb_stmt_sql[] = { + [NITB_SELECT_SUBSCR] = + "SELECT imsi, id, extension, authorized" + " FROM Subscriber" + " ORDER BY id", + [NITB_SELECT_AUTH_KEYS] = + "SELECT algorithm_id, a3a8_ki from authkeys" + " WHERE subscriber_id = $subscr_id", +}; + +sqlite3_stmt *nitb_stmt[ARRAY_SIZE(nitb_stmt_sql)] = {}; + +size_t _dbd_decode_binary(const unsigned char *in, unsigned char *out); + +void import_nitb_subscr_aud(sqlite3 *nitb_db, const char *imsi, int64_t nitb_id, int64_t hlr_id) +{ + int rc; + struct db_context *dbc = g_hlr_db_tool_ctx->dbc; + sqlite3_stmt *stmt; + + int count = 0; + + stmt = nitb_stmt[NITB_SELECT_AUTH_KEYS]; + if (!db_bind_int(stmt, NULL, nitb_id)) + return; + + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + const void *blob; + unsigned int blob_size; + static unsigned char buf[4096]; + static char ki[128]; + int decoded_size; + struct sub_auth_data_str aud2g = { + .type = OSMO_AUTH_TYPE_GSM, + .algo = OSMO_AUTH_ALG_NONE, + .u.gsm.ki = ki, + }; + + aud2g.algo = sqlite3_column_int(stmt, 0); + + if (count) { + LOGP(DDB, LOGL_ERROR, + "Warning: subscriber has more than one auth key," + " importing only the first key, for IMSI=%s\n", + imsi); + break; + } + + blob = sqlite3_column_blob(stmt, 1); + blob_size = sqlite3_column_bytes(stmt, 1); + + if (blob_size > sizeof(buf)) { + LOGP(DDB, LOGL_ERROR, + "OsmoNITB import to %s: Cannot import auth data for IMSI %s:" + " too large blob: %u\n", + dbc->fname, imsi, blob_size); + db_remove_reset(stmt); + continue; + } + + decoded_size = _dbd_decode_binary(blob, buf); + osmo_strlcpy(ki, osmo_hexdump_nospc(buf, decoded_size), sizeof(ki)); + + db_subscr_update_aud_by_id(dbc, hlr_id, &aud2g); + count ++; + } + + if (rc != SQLITE_DONE && rc != SQLITE_ROW) { + LOGP(DDB, LOGL_ERROR, "OsmoNITB DB: SQL error: (%d) %s," + " during stmt '%s'", + rc, sqlite3_errmsg(nitb_db), + nitb_stmt_sql[NITB_SELECT_AUTH_KEYS]); + } + + db_remove_reset(stmt); +} + +void import_nitb_subscr(sqlite3 *nitb_db, sqlite3_stmt *stmt) +{ + struct db_context *dbc = g_hlr_db_tool_ctx->dbc; + int rc; + struct hlr_subscriber subscr; + + int64_t nitb_id; + int64_t imsi; + char imsi_str[32]; + bool authorized; + + imsi = sqlite3_column_int64(stmt, 0); + + snprintf(imsi_str, sizeof(imsi_str), "%"PRId64, imsi); + + rc = db_subscr_create(dbc, imsi_str); + if (rc) { + LOGP(DDB, LOGL_ERROR, "OsmoNITB DB import to %s: failed to create IMSI %s: %d: %s\n", + dbc->fname, + imsi_str, + rc, + strerror(rc)); + /* on error, still attempt to continue */ + } + + nitb_id = sqlite3_column_int64(stmt, 1); + copy_sqlite3_text_to_buf(subscr.msisdn, stmt, 2); + authorized = sqlite3_column_int(stmt, 3) ? true : false; + + db_subscr_update_msisdn_by_imsi(dbc, imsi_str, subscr.msisdn); + db_subscr_nam(dbc, imsi_str, authorized, true); + db_subscr_nam(dbc, imsi_str, authorized, false); + + /* find the just created id */ + rc = db_subscr_get_by_imsi(dbc, imsi_str, &subscr); + if (rc) { + LOGP(DDB, LOGL_ERROR, "OsmoNITB DB import to %s: created IMSI %s," + " but failed to get new subscriber id: %d: %s\n", + dbc->fname, + imsi_str, + rc, + strerror(rc)); + return; + } + + OSMO_ASSERT(!strcmp(imsi_str, subscr.imsi)); + + import_nitb_subscr_aud(nitb_db, imsi_str, nitb_id, subscr.id); +} + +int import_nitb_db(void) +{ + int i; + int ret; + int rc; + const char *sql; + sqlite3_stmt *stmt; + + sqlite3 *nitb_db = open_nitb_db(cmdline_opts.import_nitb_db); + + if (!nitb_db) + return -1; + ret = 0; + + for (i = 0; i < ARRAY_SIZE(nitb_stmt_sql); i++) { + sql = nitb_stmt_sql[i]; + rc = sqlite3_prepare_v2(nitb_db, sql, -1, &nitb_stmt[i], NULL); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "OsmoNITB DB: Unable to prepare SQL statement '%s'\n", sql); + ret = -1; + goto out_free; + } + } + + stmt = nitb_stmt[NITB_SELECT_SUBSCR]; + + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + import_nitb_subscr(nitb_db, stmt); + /* On failure, carry on with the rest. */ + } + if (rc != SQLITE_DONE) { + LOGP(DDB, LOGL_ERROR, "OsmoNITB DB: SQL error: (%d) %s," + " during stmt '%s'", + rc, sqlite3_errmsg(nitb_db), + nitb_stmt_sql[NITB_SELECT_SUBSCR]); + goto out_free; + } + + db_remove_reset(stmt); + sqlite3_finalize(stmt); + +out_free: + sqlite3_close(nitb_db); + return ret; +} + +int main(int argc, char **argv) +{ + int rc; + int (*main_action)(void); + main_action = NULL; + + g_hlr_db_tool_ctx = talloc_zero(NULL, struct hlr_db_tool_ctx); + OSMO_ASSERT(g_hlr_db_tool_ctx); + talloc_set_name_const(g_hlr_db_tool_ctx, "OsmoHLR-db-tool"); + + rc = osmo_init_logging(&hlr_log_info); + if (rc < 0) { + fprintf(stderr, "Error initializing logging\n"); + exit(EXIT_FAILURE); + } + + handle_options(argc, argv); + + if (cmdline_opts.import_nitb_db) { + if (main_action) + goto too_many_actions; + main_action = import_nitb_db; + } + /* Future: add more main_actions, besides --import-nitb-db, here. */ + + /* Just in case any db actions need randomness */ + rc = rand_init(); + if (rc < 0) { + LOGP(DMAIN, LOGL_FATAL, "Error initializing random source\n"); + exit(EXIT_FAILURE); + } + + g_hlr_db_tool_ctx->dbc = db_open(g_hlr_db_tool_ctx, cmdline_opts.db_file); + if (!g_hlr_db_tool_ctx->dbc) { + LOGP(DMAIN, LOGL_FATAL, "Error opening database\n"); + exit(EXIT_FAILURE); + } + + osmo_init_ignore_signals(); + signal(SIGINT, &signal_hdlr); + signal(SIGUSR1, &signal_hdlr); + + rc = 0; + if (main_action) + rc = (*main_action)(); + + db_close(g_hlr_db_tool_ctx->dbc); + log_fini(); + exit(rc ? EXIT_FAILURE : EXIT_SUCCESS); + +too_many_actions: + fprintf(stderr, "Too many actions requested.\n"); + log_fini(); + exit(EXIT_FAILURE); +} + +/* stubs */ +void lu_op_alloc_conn(void) { OSMO_ASSERT(0); } +void lu_op_tx_del_subscr_data(void) { OSMO_ASSERT(0); } +void lu_op_free(void) { OSMO_ASSERT(0); } diff --git a/src/hlr_vty.c b/src/hlr_vty.c new file mode 100644 index 0000000..a5eb26f --- /dev/null +++ b/src/hlr_vty.c @@ -0,0 +1,141 @@ +/* OsmoHLR VTY implementation */ + +/* (C) 2016 sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include "hlr_vty.h" +#include "hlr_vty_subscr.h" + +static struct hlr *g_hlr = NULL; + +struct cmd_node hlr_node = { + HLR_NODE, + "%s(config-hlr)# ", + 1, +}; + +DEFUN(cfg_hlr, + cfg_hlr_cmd, + "hlr", + "Configure the HLR") +{ + vty->node = HLR_NODE; + return CMD_SUCCESS; +} + +struct cmd_node gsup_node = { + GSUP_NODE, + "%s(config-hlr-gsup)# ", + 1, +}; + +DEFUN(cfg_gsup, + cfg_gsup_cmd, + "gsup", + "Configure GSUP options") +{ + vty->node = GSUP_NODE; + return CMD_SUCCESS; +} + +static int config_write_hlr(struct vty *vty) +{ + vty_out(vty, "hlr%s", VTY_NEWLINE); + return CMD_SUCCESS; +} + +static int config_write_hlr_gsup(struct vty *vty) +{ + vty_out(vty, " gsup%s", VTY_NEWLINE); + if (g_hlr->gsup_bind_addr) + vty_out(vty, " bind ip %s%s", g_hlr->gsup_bind_addr, VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_hlr_gsup_bind_ip, + cfg_hlr_gsup_bind_ip_cmd, + "bind ip A.B.C.D", + "Listen/Bind related socket option\n" + IP_STR + "IPv4 Address to bind the GSUP interface to\n") +{ + if(g_hlr->gsup_bind_addr) + talloc_free(g_hlr->gsup_bind_addr); + g_hlr->gsup_bind_addr = talloc_strdup(g_hlr, argv[0]); + + return CMD_SUCCESS; +} + +int hlr_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case GSUP_NODE: + vty->node = HLR_NODE; + vty->index = NULL; + break; + default: + case HLR_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case CONFIG_NODE: + vty->node = ENABLE_NODE; + vty->index = NULL; + break; + } + + return vty->node; +} + +int hlr_vty_is_config_node(struct vty *vty, int node) +{ + switch (node) { + /* add items that are not config */ + case CONFIG_NODE: + return 0; + + default: + return 1; + } +} + +void hlr_vty_init(struct hlr *hlr, const struct log_info *cat) +{ + g_hlr = hlr; + + logging_vty_add_cmds(cat); + + install_element(CONFIG_NODE, &cfg_hlr_cmd); + install_node(&hlr_node, config_write_hlr); + install_default(HLR_NODE); + + install_element(HLR_NODE, &cfg_gsup_cmd); + install_node(&gsup_node, config_write_hlr_gsup); + install_default(GSUP_NODE); + + install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_cmd); + + hlr_vty_subscriber_init(hlr); +} diff --git a/src/hlr_vty.h b/src/hlr_vty.h new file mode 100644 index 0000000..cd2ff73 --- /dev/null +++ b/src/hlr_vty.h @@ -0,0 +1,37 @@ +/* OsmoHLR VTY implementation */ + +/* (C) 2016 sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include +#include +#include +#include "hlr.h" + +enum hlr_vty_node { + HLR_NODE = _LAST_OSMOVTY_NODE + 1, + GSUP_NODE, +}; + +int hlr_vty_is_config_node(struct vty *vty, int node); +int hlr_vty_go_parent(struct vty *vty); +void hlr_vty_init(struct hlr *hlr, const struct log_info *cat); diff --git a/src/hlr_vty_subscr.c b/src/hlr_vty_subscr.c new file mode 100644 index 0000000..0a9ba76 --- /dev/null +++ b/src/hlr_vty_subscr.c @@ -0,0 +1,484 @@ +/* OsmoHLR subscriber management VTY implementation */ +/* (C) 2017 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "hlr.h" +#include "db.h" + +struct vty; + +#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf)) + +static struct hlr *g_hlr = NULL; + +static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr) +{ + int rc; + struct osmo_sub_auth_data aud2g; + struct osmo_sub_auth_data aud3g; + + vty_out(vty, " ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE); + + vty_out(vty, " IMSI: %s%s", *subscr->imsi ? subscr->imsi : "none", VTY_NEWLINE); + vty_out(vty, " MSISDN: %s%s", *subscr->msisdn ? subscr->msisdn : "none", VTY_NEWLINE); + if (*subscr->vlr_number) + vty_out(vty, " VLR number: %s%s", subscr->vlr_number, VTY_NEWLINE); + if (*subscr->sgsn_number) + vty_out(vty, " SGSN number: %s%s", subscr->sgsn_number, VTY_NEWLINE); + if (*subscr->sgsn_address) + vty_out(vty, " SGSN address: %s%s", subscr->sgsn_address, VTY_NEWLINE); + if (subscr->periodic_lu_timer) + vty_out(vty, " Periodic LU timer: %u%s", subscr->periodic_lu_timer, VTY_NEWLINE); + if (subscr->periodic_rau_tau_timer) + vty_out(vty, " Periodic RAU/TAU timer: %u%s", subscr->periodic_rau_tau_timer, VTY_NEWLINE); + if (subscr->lmsi) + vty_out(vty, " LMSI: %x%s", subscr->lmsi, VTY_NEWLINE); + if (!subscr->nam_cs) + vty_out(vty, " CS disabled%s", VTY_NEWLINE); + if (subscr->ms_purged_cs) + vty_out(vty, " CS purged%s", VTY_NEWLINE); + if (!subscr->nam_ps) + vty_out(vty, " PS disabled%s", VTY_NEWLINE); + if (subscr->ms_purged_ps) + vty_out(vty, " PS purged%s", VTY_NEWLINE); + + if (!*subscr->imsi) + return; + + OSMO_ASSERT(g_hlr); + rc = db_get_auth_data(g_hlr->dbc, subscr->imsi, &aud2g, &aud3g, NULL); + + if (rc) { + if (rc == -ENOENT) { + aud2g.algo = OSMO_AUTH_ALG_NONE; + aud3g.algo = OSMO_AUTH_ALG_NONE; + } else { + vty_out(vty, "%% Error retrieving data from database (%d)%s", rc, VTY_NEWLINE); + return; + } + } + + if (aud2g.type != OSMO_AUTH_TYPE_NONE && aud2g.type != OSMO_AUTH_TYPE_GSM) { + vty_out(vty, "%% Error: 2G auth data is not of type 'GSM'%s", VTY_NEWLINE); + aud2g = (struct osmo_sub_auth_data){}; + } + + if (aud3g.type != OSMO_AUTH_TYPE_NONE && aud3g.type != OSMO_AUTH_TYPE_UMTS) { + vty_out(vty, "%% Error: 3G auth data is not of type 'UMTS'%s", VTY_NEWLINE); + aud3g = (struct osmo_sub_auth_data){}; + } + + if (aud2g.algo != OSMO_AUTH_ALG_NONE && aud2g.type != OSMO_AUTH_TYPE_NONE) { + vty_out(vty, " 2G auth: %s%s", + osmo_auth_alg_name(aud2g.algo), VTY_NEWLINE); + vty_out(vty, " KI=%s%s", + hexdump_buf(aud2g.u.gsm.ki), VTY_NEWLINE); + } + + if (aud3g.algo != OSMO_AUTH_ALG_NONE && aud3g.type != OSMO_AUTH_TYPE_NONE) { + vty_out(vty, " 3G auth: %s%s", osmo_auth_alg_name(aud3g.algo), VTY_NEWLINE); + vty_out(vty, " K=%s%s", hexdump_buf(aud3g.u.umts.k), VTY_NEWLINE); + vty_out(vty, " %s=%s%s", aud3g.u.umts.opc_is_op? "OP" : "OPC", + hexdump_buf(aud3g.u.umts.opc), VTY_NEWLINE); + vty_out(vty, " IND-bitlen=%u", aud3g.u.umts.ind_bitlen); + if (aud3g.u.umts.sqn) + vty_out(vty, " last-SQN=%"PRIu64, aud3g.u.umts.sqn); + vty_out(vty, VTY_NEWLINE); + } +} + +static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id, struct hlr_subscriber *subscr) +{ + int rc = -1; + if (strcmp(type, "imsi") == 0) + rc = db_subscr_get_by_imsi(g_hlr->dbc, id, subscr); + else if (strcmp(type, "msisdn") == 0) + rc = db_subscr_get_by_msisdn(g_hlr->dbc, id, subscr); + else if (strcmp(type, "id") == 0) + rc = db_subscr_get_by_id(g_hlr->dbc, atoll(id), subscr); + if (rc) + vty_out(vty, "%% No subscriber for %s = '%s'%s", + type, id, VTY_NEWLINE); + return rc; +} + +#define SUBSCR_CMD "subscriber " +#define SUBSCR_CMD_HELP "Subscriber management commands\n" + +#define SUBSCR_ID "(imsi|msisdn|id) IDENT " +#define SUBSCR_ID_HELP \ + "Identify subscriber by IMSI\n" \ + "Identify subscriber by MSISDN (phone number)\n" \ + "Identify subscriber by database ID\n" \ + "IMSI/MSISDN/ID of the subscriber\n" + +#define SUBSCR SUBSCR_CMD SUBSCR_ID +#define SUBSCR_HELP SUBSCR_CMD_HELP SUBSCR_ID_HELP + +#define SUBSCR_UPDATE SUBSCR "update " +#define SUBSCR_UPDATE_HELP SUBSCR_HELP "Set or update subscriber data\n" + +DEFUN(subscriber_show, + subscriber_show_cmd, + SUBSCR "show", + SUBSCR_HELP "Show subscriber information\n") +{ + struct hlr_subscriber subscr; + const char *id_type = argv[0]; + const char *id = argv[1]; + + if (get_subscr_by_argv(vty, id_type, id, &subscr)) + return CMD_WARNING; + + subscr_dump_full_vty(vty, &subscr); + return CMD_SUCCESS; +} + +DEFUN(subscriber_create, + subscriber_create_cmd, + SUBSCR_CMD "imsi IDENT create", + SUBSCR_CMD_HELP + "Create subscriber by IMSI\n" + "IMSI/MSISDN/ID of the subscriber\n") +{ + int rc; + struct hlr_subscriber subscr; + const char *imsi = argv[0]; + + if (!osmo_imsi_str_valid(imsi)) { + vty_out(vty, "%% Not a valid IMSI: %s%s", imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + rc = db_subscr_create(g_hlr->dbc, imsi); + + if (rc) { + if (rc == -EEXIST) + vty_out(vty, "%% Subscriber already exists for IMSI = %s%s", + imsi, VTY_NEWLINE); + else + vty_out(vty, "%% Error (rc=%d): cannot create subscriber for IMSI = %s%s", + rc, imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + rc = db_subscr_get_by_imsi(g_hlr->dbc, imsi, &subscr); + vty_out(vty, "%% Created subscriber %s%s", imsi, VTY_NEWLINE); + + subscr_dump_full_vty(vty, &subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_delete, + subscriber_delete_cmd, + SUBSCR "delete", + SUBSCR_HELP "Delete subscriber from database\n") +{ + struct hlr_subscriber subscr; + int rc; + const char *id_type = argv[0]; + const char *id = argv[1]; + + /* Find out the IMSI regardless of which way the caller decided to + * identify the subscriber by. */ + if (get_subscr_by_argv(vty, id_type, id, &subscr)) + return CMD_WARNING; + + rc = db_subscr_delete_by_id(g_hlr->dbc, subscr.id); + if (rc) { + vty_out(vty, "%% Error: Failed to remove subscriber for IMSI '%s'%s", + subscr.imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "%% Deleted subscriber for IMSI '%s'%s", subscr.imsi, VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(subscriber_msisdn, + subscriber_msisdn_cmd, + SUBSCR_UPDATE "msisdn MSISDN", + SUBSCR_UPDATE_HELP + "Set MSISDN (phone number) of the subscriber\n" + "New MSISDN (phone number)\n") +{ + struct hlr_subscriber subscr; + const char *id_type = argv[0]; + const char *id = argv[1]; + const char *msisdn = argv[2]; + + if (strlen(msisdn) > sizeof(subscr.msisdn) - 1) { + vty_out(vty, "%% MSISDN is too long, max. %zu characters are allowed%s", + sizeof(subscr.msisdn)-1, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!osmo_msisdn_str_valid(msisdn)) { + vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE); + return CMD_WARNING; + } + + if (get_subscr_by_argv(vty, id_type, id, &subscr)) + return CMD_WARNING; + + if (db_subscr_update_msisdn_by_imsi(g_hlr->dbc, subscr.imsi, msisdn)) { + vty_out(vty, "%% Error: cannot update MSISDN for subscriber IMSI='%s'%s", + subscr.imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "%% Updated subscriber IMSI='%s' to MSISDN='%s'%s", + subscr.imsi, msisdn, VTY_NEWLINE); + return CMD_SUCCESS; +} + +static bool is_hexkey_valid(struct vty *vty, const char *label, + const char *hex_str, int minlen, int maxlen) +{ + if (osmo_is_hexstr(hex_str, minlen * 2, maxlen * 2, true)) + return true; + vty_out(vty, "%% Invalid value for %s: '%s'%s", label, hex_str, VTY_NEWLINE); + return false; +} + +#define AUTH_ALG_TYPES_2G "(comp128v1|comp128v2|comp128v3|xor)" +#define AUTH_ALG_TYPES_2G_HELP \ + "Use COMP128v1 algorithm\n" \ + "Use COMP128v2 algorithm\n" \ + "Use COMP128v3 algorithm\n" \ + "Use XOR algorithm\n" + +#define AUTH_ALG_TYPES_3G "milenage" +#define AUTH_ALG_TYPES_3G_HELP \ + "Use Milenage algorithm\n" + +#define A38_XOR_MIN_KEY_LEN 12 +#define A38_XOR_MAX_KEY_LEN 16 +#define A38_COMP128_KEY_LEN 16 + +#define MILENAGE_KEY_LEN 16 + +static bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo, + int *minlen, int *maxlen) +{ + if (!strcasecmp(alg_str, "none")) { + *algo = OSMO_AUTH_ALG_NONE; + *minlen = *maxlen = 0; + } else if (!strcasecmp(alg_str, "comp128v1")) { + *algo = OSMO_AUTH_ALG_COMP128v1; + *minlen = *maxlen = A38_COMP128_KEY_LEN; + } else if (!strcasecmp(alg_str, "comp128v2")) { + *algo = OSMO_AUTH_ALG_COMP128v2; + *minlen = *maxlen = A38_COMP128_KEY_LEN; + } else if (!strcasecmp(alg_str, "comp128v3")) { + *algo = OSMO_AUTH_ALG_COMP128v3; + *minlen = *maxlen = A38_COMP128_KEY_LEN; + } else if (!strcasecmp(alg_str, "xor")) { + *algo = OSMO_AUTH_ALG_XOR; + *minlen = A38_XOR_MIN_KEY_LEN; + *maxlen = A38_XOR_MAX_KEY_LEN; + } else if (!strcasecmp(alg_str, "milenage")) { + *algo = OSMO_AUTH_ALG_MILENAGE; + *minlen = *maxlen = MILENAGE_KEY_LEN; + } else + return false; + return true; +} + +DEFUN(subscriber_no_aud2g, + subscriber_no_aud2g_cmd, + SUBSCR_UPDATE "aud2g none", + SUBSCR_UPDATE_HELP + "Set 2G authentication data\n" + "Delete 2G authentication data\n") +{ + struct hlr_subscriber subscr; + int rc; + const char *id_type = argv[0]; + const char *id = argv[1]; + struct sub_auth_data_str aud = { + .type = OSMO_AUTH_TYPE_GSM, + .algo = OSMO_AUTH_ALG_NONE, + }; + + if (get_subscr_by_argv(vty, id_type, id, &subscr)) + return CMD_WARNING; + + rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud); + + if (rc) { + vty_out(vty, "%% Error: cannot disable 2G auth data for IMSI='%s'%s", + subscr.imsi, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(subscriber_aud2g, + subscriber_aud2g_cmd, + SUBSCR_UPDATE "aud2g " AUTH_ALG_TYPES_2G " ki KI", + SUBSCR_UPDATE_HELP + "Set 2G authentication data\n" + AUTH_ALG_TYPES_2G_HELP + "Set Ki Encryption Key\n" "Ki as 32 hexadecimal characters\n") +{ + struct hlr_subscriber subscr; + int rc; + int minlen = 0; + int maxlen = 0; + const char *id_type = argv[0]; + const char *id = argv[1]; + const char *alg_type = argv[2]; + const char *ki = argv[3]; + struct sub_auth_data_str aud2g = { + .type = OSMO_AUTH_TYPE_GSM, + .u.gsm.ki = ki, + }; + + if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen)) { + vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!is_hexkey_valid(vty, "KI", aud2g.u.gsm.ki, minlen, maxlen)) + return CMD_WARNING; + + if (get_subscr_by_argv(vty, id_type, id, &subscr)) + return CMD_WARNING; + + rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud2g); + + if (rc) { + vty_out(vty, "%% Error: cannot set 2G auth data for IMSI='%s'%s", + subscr.imsi, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(subscriber_no_aud3g, + subscriber_no_aud3g_cmd, + SUBSCR_UPDATE "aud3g none", + SUBSCR_UPDATE_HELP + "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n" + "Delete 3G authentication data\n") +{ + struct hlr_subscriber subscr; + int rc; + const char *id_type = argv[0]; + const char *id = argv[1]; + struct sub_auth_data_str aud = { + .type = OSMO_AUTH_TYPE_UMTS, + .algo = OSMO_AUTH_ALG_NONE, + }; + + if (get_subscr_by_argv(vty, id_type, id, &subscr)) + return CMD_WARNING; + + rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud); + + if (rc) { + vty_out(vty, "%% Error: cannot disable 3G auth data for IMSI='%s'%s", + subscr.imsi, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(subscriber_aud3g, + subscriber_aud3g_cmd, + SUBSCR_UPDATE "aud3g " AUTH_ALG_TYPES_3G + " k K" + " (op|opc) OP_C" + " [ind-bitlen] [<0-28>]", + SUBSCR_UPDATE_HELP + "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n" + AUTH_ALG_TYPES_3G_HELP + "Set Encryption Key K\n" "K as 32 hexadecimal characters\n" + "Set OP key\n" "Set OPC key\n" "OP or OPC as 32 hexadecimal characters\n" + "Set IND bit length\n" "IND bit length value (default: 5)\n") +{ + struct hlr_subscriber subscr; + int minlen = 0; + int maxlen = 0; + int rc; + const char *id_type = argv[0]; + const char *id = argv[1]; + const char *alg_type = AUTH_ALG_TYPES_3G; + const char *k = argv[2]; + bool opc_is_op = (strcasecmp("op", argv[3]) == 0); + const char *op_opc = argv[4]; + int ind_bitlen = argc > 6? atoi(argv[6]) : 5; + struct sub_auth_data_str aud3g = { + .type = OSMO_AUTH_TYPE_UMTS, + .u.umts = { + .k = k, + .opc_is_op = opc_is_op, + .opc = op_opc, + .ind_bitlen = ind_bitlen, + }, + }; + + if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen)) { + vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen)) + return CMD_WARNING; + + if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc, + MILENAGE_KEY_LEN, MILENAGE_KEY_LEN)) + return CMD_WARNING; + + if (get_subscr_by_argv(vty, id_type, id, &subscr)) + return CMD_WARNING; + + rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g); + + if (rc) { + vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s", + subscr.imsi, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +void hlr_vty_subscriber_init(struct hlr *hlr) +{ + g_hlr = hlr; + + install_element_ve(&subscriber_show_cmd); + install_element(ENABLE_NODE, &subscriber_create_cmd); + install_element(ENABLE_NODE, &subscriber_delete_cmd); + install_element(ENABLE_NODE, &subscriber_msisdn_cmd); + install_element(ENABLE_NODE, &subscriber_no_aud2g_cmd); + install_element(ENABLE_NODE, &subscriber_aud2g_cmd); + install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd); + install_element(ENABLE_NODE, &subscriber_aud3g_cmd); +} diff --git a/src/hlr_vty_subscr.h b/src/hlr_vty_subscr.h new file mode 100644 index 0000000..841db5a --- /dev/null +++ b/src/hlr_vty_subscr.h @@ -0,0 +1,3 @@ +#pragma once + +void hlr_vty_subscriber_init(struct hlr *hlr); diff --git a/src/logging.c b/src/logging.c new file mode 100644 index 0000000..f81781d --- /dev/null +++ b/src/logging.c @@ -0,0 +1,27 @@ +#include +#include "logging.h" + +const struct log_info_cat hlr_log_info_cat[] = { + [DMAIN] = { + .name = "DMAIN", + .description = "Main Program", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DDB] = { + .name = "DDB", + .description = "Database Layer", + .color = "\033[1;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DAUC] = { + .name = "DAUC", + .description = "Authentication Center", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, +}; + +const struct log_info hlr_log_info = { + .cat = hlr_log_info_cat, + .num_cat = ARRAY_SIZE(hlr_log_info_cat), +}; diff --git a/src/logging.h b/src/logging.h new file mode 100644 index 0000000..fdaf5d1 --- /dev/null +++ b/src/logging.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +enum { + DMAIN, + DDB, + DGSUP, + DAUC, +}; + +extern const struct log_info hlr_log_info; diff --git a/src/luop.c b/src/luop.c new file mode 100644 index 0000000..2966380 --- /dev/null +++ b/src/luop.c @@ -0,0 +1,290 @@ +/* OsmoHLR TX/RX lu operations */ + +/* (C) 2017 sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Harald Welte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "gsup_server.h" +#include "gsup_router.h" +#include "logging.h" +#include "luop.h" + +const struct value_string lu_state_names[] = { + { LU_S_NULL, "NULL" }, + { LU_S_LU_RECEIVED, "LU RECEIVED" }, + { LU_S_CANCEL_SENT, "CANCEL SENT" }, + { LU_S_CANCEL_ACK_RECEIVED, "CANCEL-ACK RECEIVED" }, + { LU_S_ISD_SENT, "ISD SENT" }, + { LU_S_ISD_ACK_RECEIVED, "ISD-ACK RECEIVED" }, + { LU_S_COMPLETE, "COMPLETE" }, + { 0, NULL } +}; + +/* Transmit a given GSUP message for the given LU operation */ +static void _luop_tx_gsup(struct lu_operation *luop, + const struct osmo_gsup_message *gsup) +{ + struct msgb *msg_out; + + msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP LUOP"); + osmo_gsup_encode(msg_out, gsup); + + osmo_gsup_addr_send(luop->gsup_server, luop->peer, + talloc_total_size(luop->peer), + msg_out); +} + +static inline void fill_gsup_msg(struct osmo_gsup_message *out, + const struct lu_operation *lu, + enum osmo_gsup_message_type mt) +{ + memset(out, 0, sizeof(struct osmo_gsup_message)); + if (lu) + osmo_strlcpy(out->imsi, lu->subscr.imsi, + GSM23003_IMSI_MAX_DIGITS + 1); + out->message_type = mt; +} + +/* timer call-back in case LU operation doesn't receive an response */ +static void lu_op_timer_cb(void *data) +{ + struct lu_operation *luop = data; + + DEBUGP(DMAIN, "LU OP timer expired in state %s\n", + get_value_string(lu_state_names, luop->state)); + + switch (luop->state) { + case LU_S_CANCEL_SENT: + break; + case LU_S_ISD_SENT: + break; + default: + break; + } + + lu_op_tx_error(luop, GMM_CAUSE_NET_FAIL); +} + +bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc, + const char *imsi) +{ + struct hlr_subscriber *subscr = &luop->subscr; + + if (db_subscr_get_by_imsi(dbc, imsi, subscr) < 0) + return false; + + return true; +} + +struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv) +{ + struct lu_operation *luop; + + luop = talloc_zero(srv, struct lu_operation); + OSMO_ASSERT(luop); + luop->gsup_server = srv; + luop->timer.cb = lu_op_timer_cb; + luop->timer.data = luop; + + return luop; +} + +void lu_op_free(struct lu_operation *luop) +{ + /* Only attempt to remove when it was ever added to a list. */ + if (luop->list.next) + llist_del(&luop->list); + talloc_free(luop); +} + +struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn) +{ + uint8_t *peer_addr; + struct lu_operation *luop = lu_op_alloc(conn->server); + int rc = osmo_gsup_conn_ccm_get(conn, &peer_addr, IPAC_IDTAG_SERNR); + if (rc < 0) { + lu_op_free(luop); + return NULL; + } + + luop->peer = talloc_memdup(luop, peer_addr, rc); + + return luop; +} + +/* FIXME: this doesn't seem to work at all */ +struct lu_operation *lu_op_by_imsi(const char *imsi, + const struct llist_head *lst) +{ + struct lu_operation *luop; + + llist_for_each_entry(luop, lst, list) { + if (!strcmp(imsi, luop->subscr.imsi)) + return luop; + } + return NULL; +} + +void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state) +{ + enum lu_state old_state = luop->state; + + DEBUGP(DMAIN, "LU OP state change: %s -> ", + get_value_string(lu_state_names, old_state)); + DEBUGPC(DMAIN, "%s\n", + get_value_string(lu_state_names, new_state)); + + luop->state = new_state; +} + +/* Send a msgb to a given address using routing */ +int osmo_gsup_addr_send(struct osmo_gsup_server *gs, + const uint8_t *addr, size_t addrlen, + struct msgb *msg) +{ + struct osmo_gsup_conn *conn; + + conn = gsup_route_find(gs, addr, addrlen); + if (!conn) { + DEBUGP(DMAIN, "Cannot find route for addr %s\n", addr); + msgb_free(msg); + return -ENODEV; + } + + return osmo_gsup_conn_send(conn, msg); +} + +/*! Transmit UPD_LOC_ERROR and destroy lu_operation */ +void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause) +{ + struct osmo_gsup_message gsup; + + DEBUGP(DMAIN, "%s: LU OP Tx Error (cause %s)\n", + luop->subscr.imsi, get_value_string(gsm48_gmm_cause_names, + cause)); + + fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR); + gsup.cause = cause; + + _luop_tx_gsup(luop, &gsup); + + lu_op_free(luop); +} + +/*! Transmit UPD_LOC_RESULT and destroy lu_operation */ +void lu_op_tx_ack(struct lu_operation *luop) +{ + struct osmo_gsup_message gsup; + + fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT); + //FIXME gsup.hlr_enc; + + _luop_tx_gsup(luop, &gsup); + + lu_op_free(luop); +} + +/*! Send Cancel Location to old VLR/SGSN */ +void lu_op_tx_cancel_old(struct lu_operation *luop) +{ + struct osmo_gsup_message gsup; + + OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED); + + fill_gsup_msg(&gsup, NULL, OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST); + //gsup.cause = FIXME; + //gsup.cancel_type = FIXME; + + _luop_tx_gsup(luop, &gsup); + + lu_op_statechg(luop, LU_S_CANCEL_SENT); + osmo_timer_schedule(&luop->timer, CANCEL_TIMEOUT_SECS, 0); +} + +/*! Transmit Insert Subscriber Data to new VLR/SGSN */ +void lu_op_tx_insert_subscr_data(struct lu_operation *luop) +{ + struct osmo_gsup_message gsup; + uint8_t apn[APN_MAXLEN]; + uint8_t msisdn_enc[43]; /* TODO use constant; TS 24.008 10.5.4.7 */ + int l; + + OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED || + luop->state == LU_S_CANCEL_ACK_RECEIVED); + + fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_INSERT_DATA_REQUEST); + + l = gsm48_encode_bcd_number(msisdn_enc, sizeof(msisdn_enc), 0, + luop->subscr.msisdn); + if (l < 1) { + LOGP(DMAIN, LOGL_ERROR, + "%s: Error: cannot encode MSISDN '%s'\n", + luop->subscr.imsi, luop->subscr.msisdn); + lu_op_tx_error(luop, GMM_CAUSE_PROTO_ERR_UNSPEC); + return; + } + gsup.msisdn_enc = msisdn_enc; + gsup.msisdn_enc_len = l; + + /* FIXME: deal with encoding the following data */ + gsup.hlr_enc; + + if (luop->is_ps) { + /* FIXME: PDP infos - use more fine-grained access control + instead of wildcard APN */ + l = osmo_apn_from_str(apn, sizeof(apn), "*"); + if (l > 0) { + gsup.pdp_infos[0].apn_enc = apn; + gsup.pdp_infos[0].apn_enc_len = l; + gsup.pdp_infos[0].have_info = 1; + gsup.num_pdp_infos = 1; + /* FIXME: use real value: */ + gsup.pdp_infos[0].context_id = 1; + } + } + + /* Send ISD to new VLR/SGSN */ + _luop_tx_gsup(luop, &gsup); + + lu_op_statechg(luop, LU_S_ISD_SENT); + osmo_timer_schedule(&luop->timer, ISD_TIMEOUT_SECS, 0); +} + +/*! Transmit Delete Subscriber Data to new VLR/SGSN. + * The luop is not freed. */ +void lu_op_tx_del_subscr_data(struct lu_operation *luop) +{ + struct osmo_gsup_message gsup; + + fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_DELETE_DATA_REQUEST); + + gsup.cn_domain = OSMO_GSUP_CN_DOMAIN_PS; + + /* Send ISD to new VLR/SGSN */ + _luop_tx_gsup(luop, &gsup); +} diff --git a/src/luop.h b/src/luop.h new file mode 100644 index 0000000..053a025 --- /dev/null +++ b/src/luop.h @@ -0,0 +1,83 @@ +/* OsmoHLR TX/RX lu operations */ + +/* (C) 2017 sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Harald Welte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include + +#include +#include + +#include "db.h" + +#define CANCEL_TIMEOUT_SECS 30 +#define ISD_TIMEOUT_SECS 30 + +enum lu_state { + LU_S_NULL, + LU_S_LU_RECEIVED, + LU_S_CANCEL_SENT, + LU_S_CANCEL_ACK_RECEIVED, + LU_S_ISD_SENT, + LU_S_ISD_ACK_RECEIVED, + LU_S_COMPLETE, +}; + +extern const struct value_string lu_state_names[]; + +struct lu_operation { + /*! entry in global list of location update operations */ + struct llist_head list; + /*! to which gsup_server do we belong */ + struct osmo_gsup_server *gsup_server; + /*! state of the location update */ + enum lu_state state; + /*! CS (false) or PS (true) Location Update? */ + bool is_ps; + /*! currently running timer */ + struct osmo_timer_list timer; + + /*! subscriber related to this operation */ + struct hlr_subscriber subscr; + /*! peer VLR/SGSN starting the request */ + uint8_t *peer; +}; + +int osmo_gsup_addr_send(struct osmo_gsup_server *gs, + const uint8_t *addr, size_t addrlen, + struct msgb *msg); + +struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv); +struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn); +void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state); +bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc, + const char *imsi); +struct lu_operation *lu_op_by_imsi(const char *imsi, + const struct llist_head *lst); + +void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause); +void lu_op_tx_ack(struct lu_operation *luop); +void lu_op_tx_cancel_old(struct lu_operation *luop); +void lu_op_tx_insert_subscr_data(struct lu_operation *luop); +void lu_op_tx_del_subscr_data(struct lu_operation *luop); + +void lu_op_free(struct lu_operation *luop); diff --git a/src/populate_hlr_db.pl b/src/populate_hlr_db.pl new file mode 100755 index 0000000..7be93d8 --- /dev/null +++ b/src/populate_hlr_db.pl @@ -0,0 +1,75 @@ +#!/usr/bin/perl +# +use strict; +use DBI; +my $dbh = DBI->connect("dbi:SQLite:dbname=hlr.db","",""); + +my $sth_subscr_base = $dbh->prepare("INSERT INTO subscriber (imsi, msisdn) VALUES (?, ?)"); +my $sth_subscr_get_id = $dbh->prepare("SELECT * FROM subscriber WHERE imsi = ?"); +my $sth_auc_3g = $dbh->prepare("INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, op, sqn) VALUES (?, ?, ?, ?, ?)"); +my $sth_auc_2g = $dbh->prepare("INSERT INTO auc_2g (subscriber_id, algo_id_2g, ki) VALUES (?, ?, ?)"); + +sub create_subscr_base($) +{ + my ($imsi) = @_; + my $suffix = substr($imsi, 5); + + my $msisdn = "49" . $suffix; + + return $sth_subscr_base->execute($imsi, $msisdn); +} + +sub create_auc_2g($) +{ + my ($id) = @_; + + my $ki = "000102030405060708090a0b0c0d0e0f"; + + $sth_auc_2g->execute($id, 1, $ki); +} + +sub create_auc_3g($) +{ + my ($id) = @_; + + my $k = "000102030405060708090a0b0c0d0e0f"; + my $op = "00102030405060708090a0b0c0d0e0f0"; + + $sth_auc_3g->execute($id, 5, $k, $op, 0); +} + +sub create_subscr($$$) +{ + my ($imsi, $is_2g, $is_3g) = @_; + my $suffix = substr($imsi, 5); + + create_subscr_base($imsi); + + my $id = $dbh->sqlite_last_insert_rowid(); + #$sth_subscr_get_id->execute($imsi); + #my @arr = $sth_subscr_get_id->fetchrow_array(); + #my $id = $arr[0]; + + if ($is_3g) { + create_auc_3g($id); + } + if ($is_2g) { + create_auc_2g($id); + } +} + + +my $prefix = "90179"; + +$dbh->{AutoCommit} = 0; +$dbh->do("PRAGMA synchronous = OFF"); + +for (my $i = 0; $i < 1000000; $i++) { + my $imsi = sprintf("%s%010u", $prefix, $i); + if ($i % 1000 == 0) { + printf("Creating subscriber IMSI %s\n", $imsi); + } + create_subscr($imsi, 1, 1); +} + +$dbh->commit; diff --git a/src/rand.h b/src/rand.h new file mode 100644 index 0000000..9c5aedf --- /dev/null +++ b/src/rand.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +int rand_init(void); + +int rand_get(uint8_t *rand, unsigned int len); diff --git a/src/rand_fake.c b/src/rand_fake.c new file mode 100644 index 0000000..ad0cc68 --- /dev/null +++ b/src/rand_fake.c @@ -0,0 +1,52 @@ +/* (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include +#include + +static uint8_t ctr = 0; + +static void print_msg(void) +{ + static int printed = 0; + if (!printed) { + fprintf(stderr, "Using fake random generator for deterministic " + "test results. NEVER USE THIS IN PRODUCTION\n"); + printed = 1; + } +} + +int rand_init(void) +{ + print_msg(); + return 0; +} + +int rand_get(uint8_t *rand, unsigned int len) +{ + print_msg(); + memset(rand, ctr, len); + ctr++; + return len; +} diff --git a/src/rand_urandom.c b/src/rand_urandom.c new file mode 100644 index 0000000..68243ca --- /dev/null +++ b/src/rand_urandom.c @@ -0,0 +1,38 @@ +/* (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include + +static int rand_fd = -1; +int rand_init(void) +{ + rand_fd = open("/dev/urandom", O_RDONLY); + + return rand_fd; +} + +int rand_get(uint8_t *rand, unsigned int len) +{ + return read(rand_fd, rand, len); +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..f1cc710 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,96 @@ +SUBDIRS = \ + auc \ + gsup_server \ + db \ + $(NULL) + +# The `:;' works around a Bash 3.2 bug when the output is not writeable. +$(srcdir)/package.m4: $(top_srcdir)/configure.ac + :;{ \ + echo '# Signature of the current package.' && \ + echo 'm4_define([AT_PACKAGE_NAME],' && \ + echo ' [$(PACKAGE_NAME)])' && \ + echo 'm4_define([AT_PACKAGE_TARNAME],' && \ + echo ' [$(PACKAGE_TARNAME)])' && \ + echo 'm4_define([AT_PACKAGE_VERSION],' && \ + echo ' [$(PACKAGE_VERSION)])' && \ + echo 'm4_define([AT_PACKAGE_STRING],' && \ + echo ' [$(PACKAGE_STRING)])' && \ + echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \ + echo ' [$(PACKAGE_BUGREPORT)])'; \ + echo 'm4_define([AT_PACKAGE_URL],' && \ + echo ' [$(PACKAGE_URL)])'; \ + } >'$(srcdir)/package.m4' + +EXTRA_DIST = \ + testsuite.at \ + $(srcdir)/package.m4 \ + $(TESTSUITE) \ + test_nodes.vty \ + test_subscriber.vty \ + test_subscriber.sql \ + test_subscriber.ctrl \ + $(NULL) + +TESTSUITE = $(srcdir)/testsuite + +DISTCLEANFILES = \ + atconfig \ + $(NULL) + +if ENABLE_EXT_TESTS +python-tests: +# don't run vty and ctrl tests concurrently so that the ports don't conflict + $(MAKE) vty-test + $(MAKE) ctrl-test + +VTY_TEST_DB = hlr_vty_test.db + +# To update the VTY script from current application behavior, +# pass -u to vty_script_runner.py by doing: +# make vty-test U=-u +vty-test: + -rm -f $(VTY_TEST_DB) + osmo_verify_transcript_vty.py -v \ + -n OsmoHLR -p 4258 \ + -r "$(top_builddir)/src/osmo-hlr -c $(top_srcdir)/doc/examples/osmo-hlr.cfg -l $(VTY_TEST_DB)" \ + $(U) $(srcdir)/*.vty + -rm -f $(VTY_TEST_DB) + +CTRL_TEST_DB = hlr_ctrl_test.db + +# To update the CTRL script from current application behavior, +# pass -u to ctrl_script_runner.py by doing: +# make ctrl-test U=-u +ctrl-test: + -rm -f $(CTRL_TEST_DB) + sqlite3 $(CTRL_TEST_DB) < $(top_srcdir)/sql/hlr.sql + sqlite3 $(CTRL_TEST_DB) < $(srcdir)/test_subscriber.sql + osmo_verify_transcript_ctrl.py -v \ + -p 4259 \ + -r "$(top_builddir)/src/osmo-hlr -c $(top_srcdir)/doc/examples/osmo-hlr.cfg -l $(CTRL_TEST_DB)" \ + $(U) $(srcdir)/*.ctrl + -rm -f $(CTRL_TEST_DB) + +else +python-tests: + echo "Not running python-based tests (determined at configure-time)" +endif + +check-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS) + $(MAKE) $(AM_MAKEFLAGS) python-tests + +installcheck-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \ + $(TESTSUITEFLAGS) + +clean-local: + test ! -f '$(TESTSUITE)' || \ + $(SHELL) '$(TESTSUITE)' --clean + +AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te +AUTOTEST = $(AUTOM4TE) --language=autotest +$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4 + $(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at + mv $@.tmp $@ diff --git a/tests/auc/Makefile.am b/tests/auc/Makefile.am new file mode 100644 index 0000000..9f2974f --- /dev/null +++ b/tests/auc/Makefile.am @@ -0,0 +1,57 @@ +SUBDIRS = gen_ts_55_205_test_sets + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/src \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(NULL) + +EXTRA_DIST = \ + auc_test.ok \ + auc_test.err \ + auc_ts_55_205_test_sets.ok \ + auc_ts_55_205_test_sets.err \ + $(NULL) + +check_PROGRAMS = auc_ts_55_205_test_sets + +noinst_PROGRAMS = auc_test + +auc_test_SOURCES = \ + auc_test.c \ + $(NULL) + +auc_test_LDADD = \ + $(top_srcdir)/src/auc.c \ + $(top_srcdir)/src/logging.c \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) + +auc_ts_55_205_test_sets_SOURCES = \ + $(builddir)/auc_ts_55_205_test_sets.c \ + $(NULL) + +auc_ts_55_205_test_sets_LDADD = \ + $(top_srcdir)/src/auc.c \ + $(top_srcdir)/src/logging.c \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) + +auc_ts_55_205_test_sets.c: $(top_srcdir)/tests/auc/gen_ts_55_205_test_sets/* + $(top_srcdir)/tests/auc/gen_ts_55_205_test_sets/pdftxt_2_c.py > $@ + +.PHONY: update_exp +update_exp: + $(builddir)/auc_test >"$(srcdir)/auc_test.ok" 2>"$(srcdir)/auc_test.err" + $(builddir)/auc_ts_55_205_test_sets >"$(srcdir)/auc_ts_55_205_test_sets.ok" 2>"$(srcdir)/auc_ts_55_205_test_sets.err" diff --git a/tests/auc/auc_test.c b/tests/auc/auc_test.c new file mode 100644 index 0000000..e9c114c --- /dev/null +++ b/tests/auc/auc_test.c @@ -0,0 +1,627 @@ +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "logging.h" +#include "auc.h" + +#define comment_start() fprintf(stderr, "\n===== %s\n", __func__); +#define comment_end() fprintf(stderr, "===== %s: SUCCESS\n\n", __func__); + +#define VERBOSE_ASSERT(val, expect_op, fmt) \ + do { \ + fprintf(stderr, #val " == " fmt "\n", (val)); \ + OSMO_ASSERT((val) expect_op); \ + } while (0); + +char *vec_str(const struct osmo_auth_vector *vec) +{ + static char buf[1024]; + char *pos = buf; + char *end = buf + sizeof(buf); + +#define append(what) \ + if (pos >= end) \ + return buf; \ + pos += snprintf(pos, sizeof(buf) - (pos - buf), \ + " " #what ": %s\n", \ + osmo_hexdump_nospc((void*)&vec->what, sizeof(vec->what))) + + append(rand); + append(autn); + append(ck); + append(ik); + append(res); + append(res_len); + append(kc); + append(sres); + append(auth_types); +#undef append + + return buf; +} + +#define VEC_IS(vec, expect) do { \ + char *_is = vec_str(vec); \ + if (strcmp(_is, expect)) { \ + fprintf(stderr, "MISMATCH! expected ==\n%s\n", \ + expect); \ + char *a = _is; \ + char *b = expect; \ + for (; *a && *b; a++, b++) { \ + if (*a != *b) { \ + fprintf(stderr, "mismatch at %d:\n", \ + (int)(a - _is)); \ + while (a > _is && *(a-1) != '\n') { \ + fprintf(stderr, " "); \ + a--; \ + } \ + fprintf(stderr, "v\n%s", a); \ + break; \ + } \ + } \ + OSMO_ASSERT(false); \ + } else \ + fprintf(stderr, "vector matches expectations\n"); \ + } while (0) + +uint8_t fake_rand[16] = { 0 }; +bool fake_rand_fixed = true; + +void next_rand(const char *hexstr, bool fixed) +{ + osmo_hexparse(hexstr, fake_rand, sizeof(fake_rand)); + fake_rand_fixed = fixed; +} + +int rand_get(uint8_t *rand, unsigned int len) +{ + int i; + OSMO_ASSERT(len <= sizeof(fake_rand)); + memcpy(rand, fake_rand, len); + if (!fake_rand_fixed) { + for (i = 0; i < len; i++) + fake_rand[i] += 0x11; + } + return len; +} + +static void test_gen_vectors_2g_only(void) +{ + struct osmo_sub_auth_data aud2g; + struct osmo_sub_auth_data aud3g; + struct osmo_auth_vector vec; + int rc; + + comment_start(); + + aud2g = (struct osmo_sub_auth_data){ + .type = OSMO_AUTH_TYPE_GSM, + .algo = OSMO_AUTH_ALG_COMP128v1, + }; + + osmo_hexparse("EB215756028D60E3275E613320AEC880", + aud2g.u.gsm.ki, sizeof(aud2g.u.gsm.ki)); + + aud3g = (struct osmo_sub_auth_data){ 0 }; + + next_rand("39fa2f4e3d523d8619a73b4f65c3e14d", true); + + vec = (struct osmo_auth_vector){ {0} }; + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 0, "%"PRIu64); + rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL); + VERBOSE_ASSERT(rc, == 1, "%d"); + + VEC_IS(&vec, + " rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n" + " autn: 00000000000000000000000000000000\n" + " ck: 00000000000000000000000000000000\n" + " ik: 00000000000000000000000000000000\n" + " res: 00000000000000000000000000000000\n" + " res_len: 00\n" + " kc: 241a5b16aeb8e400\n" + " sres: 429d5b27\n" + " auth_types: 01000000\n" + ); + + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 0, "%"PRIu64); + + /* even though vec is not zero-initialized, it should produce the same + * result (regardless of the umts sequence nr) */ + aud3g.u.umts.sqn = 123; + rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL); + VERBOSE_ASSERT(rc, == 1, "%d"); + + VEC_IS(&vec, + " rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n" + " autn: 00000000000000000000000000000000\n" + " ck: 00000000000000000000000000000000\n" + " ik: 00000000000000000000000000000000\n" + " res: 00000000000000000000000000000000\n" + " res_len: 00\n" + " kc: 241a5b16aeb8e400\n" + " sres: 429d5b27\n" + " auth_types: 01000000\n" + ); + + comment_end(); +} + +static void test_gen_vectors_2g_plus_3g(void) +{ + struct osmo_sub_auth_data aud2g; + struct osmo_sub_auth_data aud3g; + struct osmo_auth_vector vec; + int rc; + + comment_start(); + + aud2g = (struct osmo_sub_auth_data){ + .type = OSMO_AUTH_TYPE_GSM, + .algo = OSMO_AUTH_ALG_COMP128v1, + }; + + osmo_hexparse("EB215756028D60E3275E613320AEC880", + aud2g.u.gsm.ki, sizeof(aud2g.u.gsm.ki)); + + aud3g = (struct osmo_sub_auth_data){ + .type = OSMO_AUTH_TYPE_UMTS, + .algo = OSMO_AUTH_ALG_MILENAGE, + .u.umts.sqn = 31, + }; + + osmo_hexparse("EB215756028D60E3275E613320AEC880", + aud3g.u.umts.k, sizeof(aud3g.u.umts.k)); + osmo_hexparse("FB2A3D1B360F599ABAB99DB8669F8308", + aud3g.u.umts.opc, sizeof(aud3g.u.umts.opc)); + next_rand("39fa2f4e3d523d8619a73b4f65c3e14d", true); + + vec = (struct osmo_auth_vector){ {0} }; + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64); + rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL); + VERBOSE_ASSERT(rc, == 1, "%d"); + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 32, "%"PRIu64); + + VEC_IS(&vec, + " rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n" + " autn: 8704f5ba55d30000541dde77ea5b1d8c\n" + " ck: f64735036e5871319c679f4742a75ea1\n" + " ik: 27497388b6cb044648f396aa155b95ef\n" + " res: e229c19e791f2e410000000000000000\n" + " res_len: 08\n" + " kc: 241a5b16aeb8e400\n" + " sres: 429d5b27\n" + " auth_types: 03000000\n" + ); + + /* even though vec is not zero-initialized, it should produce the same + * result with the same sequence nr */ + aud3g.u.umts.sqn = 31; + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64); + rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL); + VERBOSE_ASSERT(rc, == 1, "%d"); + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 32, "%"PRIu64); + + VEC_IS(&vec, + " rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n" + " autn: 8704f5ba55d30000541dde77ea5b1d8c\n" + " ck: f64735036e5871319c679f4742a75ea1\n" + " ik: 27497388b6cb044648f396aa155b95ef\n" + " res: e229c19e791f2e410000000000000000\n" + " res_len: 08\n" + " kc: 241a5b16aeb8e400\n" + " sres: 429d5b27\n" + " auth_types: 03000000\n" + ); + + comment_end(); +} + +void _test_gen_vectors_3g_only__expect_vecs(struct osmo_auth_vector vecs[3]) +{ + fprintf(stderr, "[0]: "); + VEC_IS(&vecs[0], + " rand: 897210a0f7de278f0b8213098e098a3f\n" + " autn: c6b9790dad4b00000cf322869ea6a481\n" + " ck: e9922bd036718ed9e40bd1d02c3b81a5\n" + " ik: f19c20ca863137f8892326d959ec5e01\n" + " res: 9af5a557902d2db80000000000000000\n" + " res_len: 08\n" + " kc: 7526fc13c5976685\n" + " sres: 0ad888ef\n" + " auth_types: 03000000\n" + ); + fprintf(stderr, "[1]: "); + VEC_IS(&vecs[1], + " rand: 9a8321b108ef38a01c93241a9f1a9b50\n" + " autn: 79a5113eb0910000be6020540503ffc5\n" + " ck: 3686f05df057d1899c66ae4eb18cf941\n" + " ik: 79f21ed53bcb47787de57d136ff803a5\n" + " res: 43023475cb29292c0000000000000000\n" + " res_len: 08\n" + " kc: aef73dd515e86c15\n" + " sres: 882b1d59\n" + " auth_types: 03000000\n" + ); + fprintf(stderr, "[2]: "); + VEC_IS(&vecs[2], + " rand: ab9432c2190049b12da4352bb02bac61\n" + " autn: 24b018d46c3b00009c7e1b47f3a19b2b\n" + " ck: d86c3191a36fc0602e48202ef2080964\n" + " ik: 648dab72016181406243420649e63dc9\n" + " res: 010cab11cc63a6e40000000000000000\n" + " res_len: 08\n" + " kc: f0eaf8cb19e0758d\n" + " sres: cd6f0df5\n" + " auth_types: 03000000\n" + ); +} + +static void test_gen_vectors_3g_only(void) +{ + struct osmo_sub_auth_data aud2g; + struct osmo_sub_auth_data aud3g; + struct osmo_auth_vector vec; + struct osmo_auth_vector vecs[3]; + uint8_t auts[14]; + uint8_t rand_auts[16]; + int rc; + + comment_start(); + + aud2g = (struct osmo_sub_auth_data){ 0 }; + + aud3g = (struct osmo_sub_auth_data){ + .type = OSMO_AUTH_TYPE_UMTS, + .algo = OSMO_AUTH_ALG_MILENAGE, + .u.umts.sqn = 31, + }; + + osmo_hexparse("EB215756028D60E3275E613320AEC880", + aud3g.u.umts.k, sizeof(aud3g.u.umts.k)); + osmo_hexparse("FB2A3D1B360F599ABAB99DB8669F8308", + aud3g.u.umts.opc, sizeof(aud3g.u.umts.opc)); + next_rand("39fa2f4e3d523d8619a73b4f65c3e14d", true); + + vec = (struct osmo_auth_vector){ {0} }; + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64); + rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL); + VERBOSE_ASSERT(rc, == 1, "%d"); + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 32, "%"PRIu64); + + VEC_IS(&vec, + " rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n" + " autn: 8704f5ba55d30000541dde77ea5b1d8c\n" + " ck: f64735036e5871319c679f4742a75ea1\n" + " ik: 27497388b6cb044648f396aa155b95ef\n" + " res: e229c19e791f2e410000000000000000\n" + " res_len: 08\n" + " kc: 059a4f668f6fbe39\n" + " sres: 9b36efdf\n" + " auth_types: 03000000\n" + ); + + /* Note: 3GPP TS 33.102 6.8.1.2: c3 function to get GSM auth is + * KC[0..7] == CK[0..7] ^ CK[8..15] ^ IK[0..7] ^ IK[8..15] + * In [16]: hex( 0xf64735036e587131 + * ^ 0x9c679f4742a75ea1 + * ^ 0x27497388b6cb0446 + * ^ 0x48f396aa155b95ef) + * Out[16]: '0x59a4f668f6fbe39L' + * hence expecting kc: 059a4f668f6fbe39 + */ + + /* even though vec is not zero-initialized, it should produce the same + * result with the same sequence nr */ + aud3g.u.umts.sqn = 31; + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64); + rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL); + VERBOSE_ASSERT(rc, == 1, "%d"); + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 32, "%"PRIu64); + + VEC_IS(&vec, + " rand: 39fa2f4e3d523d8619a73b4f65c3e14d\n" + " autn: 8704f5ba55d30000541dde77ea5b1d8c\n" + " ck: f64735036e5871319c679f4742a75ea1\n" + " ik: 27497388b6cb044648f396aa155b95ef\n" + " res: e229c19e791f2e410000000000000000\n" + " res_len: 08\n" + " kc: 059a4f668f6fbe39\n" + " sres: 9b36efdf\n" + " auth_types: 03000000\n" + ); + + + fprintf(stderr, "- test AUTS resync\n"); + vec = (struct osmo_auth_vector){}; + aud3g.u.umts.sqn = 31; + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64); + + /* The AUTN sent was 8704f5ba55f30000d2ee44b22c8ea919 + * with the first 6 bytes being SQN ^ AK. + * K = EB215756028D60E3275E613320AEC880 + * OPC = FB2A3D1B360F599ABAB99DB8669F8308 + * RAND = 39fa2f4e3d523d8619a73b4f65c3e14d + * --milenage-f5--> + * AK = 8704f5ba55f3 + * + * The first six bytes are 8704f5ba55f3, + * and 8704f5ba55f3 ^ AK = 0. + * --> SQN = 0. + * + * Say the USIM doesn't like that, let's say it is at SQN 23. + * SQN_MS = 000000000017 + * + * AUTS = Conc(SQN_MS) || MAC-S + * Conc(SQN_MS) = SQN_MS ⊕ f5*[K](RAND) + * MAC-S = f1*[K] (SQN MS || RAND || AMF) + * + * f5*--> Conc(SQN_MS) = 000000000017 ^ 979498b1f73a + * = 979498b1f72d + * AMF = 0000 (TS 33.102 v7.0.0, 6.3.3) + * + * MAC-S = f1*[K] (000000000017 || 39fa2f4e3d523d8619a73b4f65c3e14d || 0000) + * = 3e28c59fa2e72f9c + * + * AUTS = 979498b1f72d || 3e28c59fa2e72f9c + * + * verify valid AUTS resulting in SQN 23 with: + * osmo-auc-gen -3 -a milenage -k EB215756028D60E3275E613320AEC880 \ + * -o FB2A3D1B360F599ABAB99DB8669F8308 \ + * -r 39fa2f4e3d523d8619a73b4f65c3e14d \ + * -A 979498b1f72d3e28c59fa2e72f9c + */ + + /* AUTS response by USIM */ + osmo_hexparse("979498b1f72d3e28c59fa2e72f9c", + auts, sizeof(auts)); + /* RAND sent to USIM, which AUTS was generated from */ + osmo_hexparse("39fa2f4e3d523d8619a73b4f65c3e14d", + rand_auts, sizeof(rand_auts)); + /* new RAND token for the next key */ + next_rand("897210a0f7de278f0b8213098e098a3f", true); + rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, rand_auts, auts); + VERBOSE_ASSERT(rc, == 1, "%d"); + /* The USIM's last sqn was 23, the calculated vector was 24 */ + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 24, "%"PRIu64); + + VEC_IS(&vec, + " rand: 897210a0f7de278f0b8213098e098a3f\n" + " autn: c6b9790dad4b00000cf322869ea6a481\n" + " ck: e9922bd036718ed9e40bd1d02c3b81a5\n" + " ik: f19c20ca863137f8892326d959ec5e01\n" + " res: 9af5a557902d2db80000000000000000\n" + " res_len: 08\n" + " kc: 7526fc13c5976685\n" + " sres: 0ad888ef\n" + " auth_types: 03000000\n" + ); + + + fprintf(stderr, "- verify N vectors with AUTS resync" + " == N vectors without AUTS\n" + "First just set rand and sqn = 23, and compute 3 vectors\n"); + next_rand("897210a0f7de278f0b8213098e098a3f", false); + aud3g.u.umts.sqn = 23; + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 23, "%"PRIu64); + + memset(vecs, 0, sizeof(vecs)); + rc = auc_compute_vectors(vecs, 3, &aud2g, &aud3g, NULL, NULL); + VERBOSE_ASSERT(rc, == 3, "%d"); + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 26, "%"PRIu64); + + _test_gen_vectors_3g_only__expect_vecs(vecs); + + fprintf(stderr, "Now reach sqn = 23 with AUTS and expect the same\n"); + /* AUTS response by USIM */ + osmo_hexparse("979498b1f72d3e28c59fa2e72f9c", + auts, sizeof(auts)); + /* RAND sent to USIM, which AUTS was generated from */ + osmo_hexparse("39fa2f4e3d523d8619a73b4f65c3e14d", + rand_auts, sizeof(rand_auts)); + next_rand("897210a0f7de278f0b8213098e098a3f", false); + rc = auc_compute_vectors(vecs, 3, &aud2g, &aud3g, rand_auts, auts); + + _test_gen_vectors_3g_only__expect_vecs(vecs); + + comment_end(); +} + +void test_gen_vectors_bad_args() +{ + struct osmo_auth_vector vec; + uint8_t auts[14]; + uint8_t rand_auts[16]; + int rc; + int i; + + struct osmo_sub_auth_data aud2g = { + .type = OSMO_AUTH_TYPE_GSM, + .algo = OSMO_AUTH_ALG_COMP128v1, + }; + + struct osmo_sub_auth_data aud3g = { + .type = OSMO_AUTH_TYPE_UMTS, + .algo = OSMO_AUTH_ALG_MILENAGE, + }; + + struct osmo_sub_auth_data aud2g_noalg = { + .type = OSMO_AUTH_TYPE_GSM, + .algo = OSMO_AUTH_ALG_NONE, + }; + + struct osmo_sub_auth_data aud3g_noalg = { + .type = OSMO_AUTH_TYPE_UMTS, + .algo = OSMO_AUTH_ALG_NONE, + }; + + struct osmo_sub_auth_data aud_notype = { + .type = OSMO_AUTH_TYPE_NONE, + .algo = OSMO_AUTH_ALG_MILENAGE, + }; + + struct osmo_sub_auth_data no_aud = { + .type = OSMO_AUTH_TYPE_NONE, + .algo = OSMO_AUTH_ALG_NONE, + }; + + struct { + struct osmo_sub_auth_data *aud2g; + struct osmo_sub_auth_data *aud3g; + uint8_t *rand_auts; + uint8_t *auts; + const char *label; + } tests[] = { + { NULL, NULL, NULL, NULL, "no auth data (a)"}, + { NULL, &aud3g_noalg, NULL, NULL, "no auth data (b)"}, + { NULL, &aud_notype, NULL, NULL, "no auth data (c)"}, + { NULL, &no_aud, NULL, NULL, "no auth data (d)"}, + { &aud2g_noalg, NULL, NULL, NULL, "no auth data (e)"}, + { &aud2g_noalg, &aud3g_noalg, NULL, NULL, "no auth data (f)"}, + { &aud2g_noalg, &aud_notype, NULL, NULL, "no auth data (g)"}, + { &aud2g_noalg, &no_aud, NULL, NULL, "no auth data (h)"}, + { &aud_notype, NULL, NULL, NULL, "no auth data (i)"}, + { &aud_notype, &aud3g_noalg, NULL, NULL, "no auth data (j)"}, + { &aud_notype, &aud_notype, NULL, NULL, "no auth data (k)"}, + { &aud_notype, &no_aud, NULL, NULL, "no auth data (l)"}, + { &no_aud, NULL, NULL, NULL, "no auth data (m)"}, + { &no_aud, &aud3g_noalg, NULL, NULL, "no auth data (n)"}, + { &no_aud, &aud_notype, NULL, NULL, "no auth data (o)"}, + { &no_aud, &no_aud, NULL, NULL, "no auth data (p)"}, + { &aud3g, NULL, NULL, NULL, "wrong auth data type (a)"}, + { &aud3g, &aud3g_noalg, NULL, NULL, "wrong auth data type (b)"}, + { &aud3g, &aud_notype, NULL, NULL, "wrong auth data type (c)"}, + { &aud3g, &no_aud, NULL, NULL, "wrong auth data type (d)"}, + { NULL, &aud2g, NULL, NULL, "wrong auth data type (e)"}, + { &aud3g_noalg, &aud2g, NULL, NULL, "wrong auth data type (f)"}, + { &aud_notype, &aud2g, NULL, NULL, "wrong auth data type (g)"}, + { &no_aud, &aud2g, NULL, NULL, "wrong auth data type (h)"}, + { &aud3g, &aud2g, NULL, NULL, "wrong auth data type (i)"}, + { &aud3g, &aud3g, NULL, NULL, "wrong auth data type (j)"}, + { &aud2g, &aud2g, NULL, NULL, "wrong auth data type (k)"}, + { &aud2g, NULL, rand_auts, auts, "AUTS for 2G-only (a)"}, + { &aud2g, &aud3g_noalg, rand_auts, auts, "AUTS for 2G-only (b)"}, + { &aud2g, &aud_notype, rand_auts, auts, "AUTS for 2G-only (c)"}, + { &aud2g, &no_aud, rand_auts, auts, "AUTS for 2G-only (d)"}, + { NULL, &aud3g, NULL, auts, "incomplete AUTS (a)"}, + { NULL, &aud3g, rand_auts, NULL, "incomplete AUTS (b)"}, + { &aud2g, &aud3g, NULL, auts, "incomplete AUTS (c)"}, + { &aud2g, &aud3g, rand_auts, NULL, "incomplete AUTS (d)"}, + }; + + comment_start(); + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + fprintf(stderr, "\n- %s\n", tests[i].label); + rc = auc_compute_vectors(&vec, 1, + tests[i].aud2g, + tests[i].aud3g, + tests[i].rand_auts, + tests[i].auts); + VERBOSE_ASSERT(rc, < 0, "%d"); + } + + comment_end(); +} + +static struct { + bool verbose; +} cmdline_opts = { + .verbose = false, +}; + +static void print_help(const char *program) +{ + printf("Usage:\n" + " %s [-v] [N [N...]]\n" + "Options:\n" + " -h --help show this text.\n" + " -v --verbose print source file and line numbers\n", + program + ); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"verbose", 1, 0, 'v'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hv", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(argv[0]); + exit(0); + case 'v': + cmdline_opts.verbose = true; + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + exit(-1); + break; + } + } + + if (optind < argc) { + fprintf(stderr, "too many args\n"); + exit(-1); + } +} + +int main(int argc, char **argv) +{ + printf("auc_3g_test.c\n"); + + handle_options(argc, argv); + + osmo_init_logging(&hlr_log_info); + log_set_print_filename(osmo_stderr_target, cmdline_opts.verbose); + log_set_print_timestamp(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_parse_category_mask(osmo_stderr_target, "DMAIN,1:DDB,1:DAUC,1"); + + test_gen_vectors_2g_only(); + test_gen_vectors_2g_plus_3g(); + test_gen_vectors_3g_only(); + test_gen_vectors_bad_args(); + + printf("Done\n"); + return 0; +} diff --git a/tests/auc/auc_test.err b/tests/auc/auc_test.err new file mode 100644 index 0000000..5263d04 --- /dev/null +++ b/tests/auc/auc_test.err @@ -0,0 +1,353 @@ + +===== test_gen_vectors_2g_only +aud3g.u.umts.sqn == 0 +DAUC Computing 1 auth vector: 2G only +DAUC 2G: ki = eb215756028d60e3275e613320aec880 +DAUC vector [0]: rand = 39fa2f4e3d523d8619a73b4f65c3e14d +DAUC vector [0]: kc = 241a5b16aeb8e400 +DAUC vector [0]: sres = 429d5b27 +DAUC vector [0]: auth_types = 0x1 +rc == 1 +vector matches expectations +aud3g.u.umts.sqn == 0 +DAUC Computing 1 auth vector: 2G only +DAUC 2G: ki = eb215756028d60e3275e613320aec880 +DAUC vector [0]: rand = 39fa2f4e3d523d8619a73b4f65c3e14d +DAUC vector [0]: kc = 241a5b16aeb8e400 +DAUC vector [0]: sres = 429d5b27 +DAUC vector [0]: auth_types = 0x1 +rc == 1 +vector matches expectations +===== test_gen_vectors_2g_only: SUCCESS + + +===== test_gen_vectors_2g_plus_3g +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G + separate 2G +DAUC 3G: k = eb215756028d60e3275e613320aec880 +DAUC 3G: opc = fb2a3d1b360f599abab99db8669f8308 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC 2G: ki = eb215756028d60e3275e613320aec880 +DAUC vector [0]: rand = 39fa2f4e3d523d8619a73b4f65c3e14d +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 8704f5ba55d30000541dde77ea5b1d8c +DAUC vector [0]: ck = f64735036e5871319c679f4742a75ea1 +DAUC vector [0]: ik = 27497388b6cb044648f396aa155b95ef +DAUC vector [0]: res = e229c19e791f2e410000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: deriving 2G from 3G +DAUC vector [0]: kc = 241a5b16aeb8e400 +DAUC vector [0]: sres = 429d5b27 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G + separate 2G +DAUC 3G: k = eb215756028d60e3275e613320aec880 +DAUC 3G: opc = fb2a3d1b360f599abab99db8669f8308 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC 2G: ki = eb215756028d60e3275e613320aec880 +DAUC vector [0]: rand = 39fa2f4e3d523d8619a73b4f65c3e14d +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 8704f5ba55d30000541dde77ea5b1d8c +DAUC vector [0]: ck = f64735036e5871319c679f4742a75ea1 +DAUC vector [0]: ik = 27497388b6cb044648f396aa155b95ef +DAUC vector [0]: res = e229c19e791f2e410000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: deriving 2G from 3G +DAUC vector [0]: kc = 241a5b16aeb8e400 +DAUC vector [0]: sres = 429d5b27 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_gen_vectors_2g_plus_3g: SUCCESS + + +===== test_gen_vectors_3g_only +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = eb215756028d60e3275e613320aec880 +DAUC 3G: opc = fb2a3d1b360f599abab99db8669f8308 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 39fa2f4e3d523d8619a73b4f65c3e14d +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 8704f5ba55d30000541dde77ea5b1d8c +DAUC vector [0]: ck = f64735036e5871319c679f4742a75ea1 +DAUC vector [0]: ik = 27497388b6cb044648f396aa155b95ef +DAUC vector [0]: res = e229c19e791f2e410000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 059a4f668f6fbe39 +DAUC vector [0]: sres = 9b36efdf +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = eb215756028d60e3275e613320aec880 +DAUC 3G: opc = fb2a3d1b360f599abab99db8669f8308 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 39fa2f4e3d523d8619a73b4f65c3e14d +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 8704f5ba55d30000541dde77ea5b1d8c +DAUC vector [0]: ck = f64735036e5871319c679f4742a75ea1 +DAUC vector [0]: ik = 27497388b6cb044648f396aa155b95ef +DAUC vector [0]: res = e229c19e791f2e410000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 059a4f668f6fbe39 +DAUC vector [0]: sres = 9b36efdf +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +- test AUTS resync +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys), with AUTS resync +DAUC 3G: k = eb215756028d60e3275e613320aec880 +DAUC 3G: opc = fb2a3d1b360f599abab99db8669f8308 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 897210a0f7de278f0b8213098e098a3f +DAUC vector [0]: resync: auts = 979498b1f72d3e28c59fa2e72f9c +DAUC vector [0]: resync: rand_auts = 39fa2f4e3d523d8619a73b4f65c3e14d +DAUC vector [0]: sqn = 24 +DAUC vector [0]: autn = c6b9790dad4b00000cf322869ea6a481 +DAUC vector [0]: ck = e9922bd036718ed9e40bd1d02c3b81a5 +DAUC vector [0]: ik = f19c20ca863137f8892326d959ec5e01 +DAUC vector [0]: res = 9af5a557902d2db80000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 7526fc13c5976685 +DAUC vector [0]: sres = 0ad888ef +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 24 +vector matches expectations +- verify N vectors with AUTS resync == N vectors without AUTS +First just set rand and sqn = 23, and compute 3 vectors +aud3g.u.umts.sqn == 23 +DAUC Computing 3 auth vectors: 3G only (2G derived from 3G keys) +DAUC 3G: k = eb215756028d60e3275e613320aec880 +DAUC 3G: opc = fb2a3d1b360f599abab99db8669f8308 +DAUC 3G: for sqn ind 0, previous sqn was 23 +DAUC vector [0]: rand = 897210a0f7de278f0b8213098e098a3f +DAUC vector [0]: sqn = 24 +DAUC vector [0]: autn = c6b9790dad4b00000cf322869ea6a481 +DAUC vector [0]: ck = e9922bd036718ed9e40bd1d02c3b81a5 +DAUC vector [0]: ik = f19c20ca863137f8892326d959ec5e01 +DAUC vector [0]: res = 9af5a557902d2db80000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 7526fc13c5976685 +DAUC vector [0]: sres = 0ad888ef +DAUC vector [0]: auth_types = 0x3 +DAUC vector [1]: rand = 9a8321b108ef38a01c93241a9f1a9b50 +DAUC vector [1]: sqn = 25 +DAUC vector [1]: autn = 79a5113eb0910000be6020540503ffc5 +DAUC vector [1]: ck = 3686f05df057d1899c66ae4eb18cf941 +DAUC vector [1]: ik = 79f21ed53bcb47787de57d136ff803a5 +DAUC vector [1]: res = 43023475cb29292c0000000000000000 +DAUC vector [1]: res_len = 8 +DAUC vector [1]: kc = aef73dd515e86c15 +DAUC vector [1]: sres = 882b1d59 +DAUC vector [1]: auth_types = 0x3 +DAUC vector [2]: rand = ab9432c2190049b12da4352bb02bac61 +DAUC vector [2]: sqn = 26 +DAUC vector [2]: autn = 24b018d46c3b00009c7e1b47f3a19b2b +DAUC vector [2]: ck = d86c3191a36fc0602e48202ef2080964 +DAUC vector [2]: ik = 648dab72016181406243420649e63dc9 +DAUC vector [2]: res = 010cab11cc63a6e40000000000000000 +DAUC vector [2]: res_len = 8 +DAUC vector [2]: kc = f0eaf8cb19e0758d +DAUC vector [2]: sres = cd6f0df5 +DAUC vector [2]: auth_types = 0x3 +rc == 3 +aud3g.u.umts.sqn == 26 +[0]: vector matches expectations +[1]: vector matches expectations +[2]: vector matches expectations +Now reach sqn = 23 with AUTS and expect the same +DAUC Computing 3 auth vectors: 3G only (2G derived from 3G keys), with AUTS resync +DAUC 3G: k = eb215756028d60e3275e613320aec880 +DAUC 3G: opc = fb2a3d1b360f599abab99db8669f8308 +DAUC 3G: for sqn ind 0, previous sqn was 26 +DAUC vector [0]: rand = 897210a0f7de278f0b8213098e098a3f +DAUC vector [0]: resync: auts = 979498b1f72d3e28c59fa2e72f9c +DAUC vector [0]: resync: rand_auts = 39fa2f4e3d523d8619a73b4f65c3e14d +DAUC vector [0]: sqn = 24 +DAUC vector [0]: autn = c6b9790dad4b00000cf322869ea6a481 +DAUC vector [0]: ck = e9922bd036718ed9e40bd1d02c3b81a5 +DAUC vector [0]: ik = f19c20ca863137f8892326d959ec5e01 +DAUC vector [0]: res = 9af5a557902d2db80000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 7526fc13c5976685 +DAUC vector [0]: sres = 0ad888ef +DAUC vector [0]: auth_types = 0x3 +DAUC vector [1]: rand = 9a8321b108ef38a01c93241a9f1a9b50 +DAUC vector [1]: sqn = 25 +DAUC vector [1]: autn = 79a5113eb0910000be6020540503ffc5 +DAUC vector [1]: ck = 3686f05df057d1899c66ae4eb18cf941 +DAUC vector [1]: ik = 79f21ed53bcb47787de57d136ff803a5 +DAUC vector [1]: res = 43023475cb29292c0000000000000000 +DAUC vector [1]: res_len = 8 +DAUC vector [1]: kc = aef73dd515e86c15 +DAUC vector [1]: sres = 882b1d59 +DAUC vector [1]: auth_types = 0x3 +DAUC vector [2]: rand = ab9432c2190049b12da4352bb02bac61 +DAUC vector [2]: sqn = 26 +DAUC vector [2]: autn = 24b018d46c3b00009c7e1b47f3a19b2b +DAUC vector [2]: ck = d86c3191a36fc0602e48202ef2080964 +DAUC vector [2]: ik = 648dab72016181406243420649e63dc9 +DAUC vector [2]: res = 010cab11cc63a6e40000000000000000 +DAUC vector [2]: res_len = 8 +DAUC vector [2]: kc = f0eaf8cb19e0758d +DAUC vector [2]: sres = cd6f0df5 +DAUC vector [2]: auth_types = 0x3 +[0]: vector matches expectations +[1]: vector matches expectations +[2]: vector matches expectations +===== test_gen_vectors_3g_only: SUCCESS + + +===== test_gen_vectors_bad_args + +- no auth data (a) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (b) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (c) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (d) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (e) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (f) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (g) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (h) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (i) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (j) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (k) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (l) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (m) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (n) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (o) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- no auth data (p) +DAUC auc_compute_vectors() called with neither 2G nor 3G auth data available +rc == -1 + +- wrong auth data type (a) +DAUC auc_compute_vectors() called with non-2G auth data passed for aud2g arg +rc == -1 + +- wrong auth data type (b) +DAUC auc_compute_vectors() called with non-2G auth data passed for aud2g arg +rc == -1 + +- wrong auth data type (c) +DAUC auc_compute_vectors() called with non-2G auth data passed for aud2g arg +rc == -1 + +- wrong auth data type (d) +DAUC auc_compute_vectors() called with non-2G auth data passed for aud2g arg +rc == -1 + +- wrong auth data type (e) +DAUC auc_compute_vectors() called with non-3G auth data passed for aud3g arg +rc == -1 + +- wrong auth data type (f) +DAUC auc_compute_vectors() called with non-3G auth data passed for aud3g arg +rc == -1 + +- wrong auth data type (g) +DAUC auc_compute_vectors() called with non-3G auth data passed for aud3g arg +rc == -1 + +- wrong auth data type (h) +DAUC auc_compute_vectors() called with non-3G auth data passed for aud3g arg +rc == -1 + +- wrong auth data type (i) +DAUC auc_compute_vectors() called with non-2G auth data passed for aud2g arg +rc == -1 + +- wrong auth data type (j) +DAUC auc_compute_vectors() called with non-2G auth data passed for aud2g arg +rc == -1 + +- wrong auth data type (k) +DAUC auc_compute_vectors() called with non-3G auth data passed for aud3g arg +rc == -1 + +- AUTS for 2G-only (a) +DAUC auc_compute_vectors() with AUTS called but no 3G auth data passed +rc == -1 + +- AUTS for 2G-only (b) +DAUC auc_compute_vectors() with AUTS called but no 3G auth data passed +rc == -1 + +- AUTS for 2G-only (c) +DAUC auc_compute_vectors() with AUTS called but no 3G auth data passed +rc == -1 + +- AUTS for 2G-only (d) +DAUC auc_compute_vectors() with AUTS called but no 3G auth data passed +rc == -1 + +- incomplete AUTS (a) +DAUC auc_compute_vectors() with only one of AUTS and AUTS_RAND given, need both or neither +rc == -1 + +- incomplete AUTS (b) +DAUC auc_compute_vectors() with only one of AUTS and AUTS_RAND given, need both or neither +rc == -1 + +- incomplete AUTS (c) +DAUC auc_compute_vectors() with only one of AUTS and AUTS_RAND given, need both or neither +rc == -1 + +- incomplete AUTS (d) +DAUC auc_compute_vectors() with only one of AUTS and AUTS_RAND given, need both or neither +rc == -1 +===== test_gen_vectors_bad_args: SUCCESS + diff --git a/tests/auc/auc_test.ok b/tests/auc/auc_test.ok new file mode 100644 index 0000000..81272cd --- /dev/null +++ b/tests/auc/auc_test.ok @@ -0,0 +1,2 @@ +auc_3g_test.c +Done diff --git a/tests/auc/auc_ts_55_205_test_sets.err b/tests/auc/auc_ts_55_205_test_sets.err new file mode 100644 index 0000000..1c5ad60 --- /dev/null +++ b/tests/auc/auc_ts_55_205_test_sets.err @@ -0,0 +1,418 @@ + +===== test_set_1 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 465b5ce8b199b49faa5f0a2ee238a6bc +DAUC 3G: opc = cd63cb71954a9f4e48a5994e37a02baf +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 23553cbe9637a89d218ae64dae47bf35 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = aa689c64835000002bb2bf2f1faba139 +DAUC vector [0]: ck = b40ba9a3c58b2a05bbf0d987b21bf8cb +DAUC vector [0]: ik = f769bcd751044604127672711c6d3441 +DAUC vector [0]: res = a54211d5e3ba50bf0000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = eae4be823af9a08b +DAUC vector [0]: sres = 46f8416a +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_1: SUCCESS + + +===== test_set_2 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = fec86ba6eb707ed08905757b1bb44b8f +DAUC 3G: opc = 1006020f0a478bf6b699f15c062e42b3 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 9f7c8d021accf4db213ccff0c7f71a6a +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 33484dc2134b000091ec125f4840ed64 +DAUC vector [0]: ck = 5dbdbb2954e8f3cde665b046179a5098 +DAUC vector [0]: ik = 59a92d3b476a0443487055cf88b2307b +DAUC vector [0]: res = 8011c48c0c214ed20000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = aa01739b8caa976d +DAUC vector [0]: sres = 8c308a5e +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_2: SUCCESS + + +===== test_set_3 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 9e5944aea94b81165c82fbf9f32db751 +DAUC 3G: opc = a64a507ae1a2a98bb88eb4210135dc87 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = ce83dbc54ac0274a157c17f80d017bd6 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = f0b9c08ad00e00005da4ccbbdfa29310 +DAUC vector [0]: ck = e203edb3971574f5a94b0d61b816345d +DAUC vector [0]: ik = 0c4524adeac041c4dd830d20854fc46b +DAUC vector [0]: res = f365cd683cd92e960000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 9a8ec95f408cc507 +DAUC vector [0]: sres = cfbce3fe +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_3: SUCCESS + + +===== test_set_4 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 4ab1deb05ca6ceb051fc98e77d026a84 +DAUC 3G: opc = dcf07cbd51855290b92a07a9891e523e +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 74b0cd6031a1c8339b2b6ce2b8c4a186 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 31e11a60913800006a7003718d5d82e5 +DAUC vector [0]: ck = 7657766b373d1c2138f307e3de9242f9 +DAUC vector [0]: ik = 1c42e960d89b8fa99f2744e0708ccb53 +DAUC vector [0]: res = 5860fc1bce351e7e0000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = cdc1dc0841b81a22 +DAUC vector [0]: sres = 9655e265 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_4: SUCCESS + + +===== test_set_5 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 6c38a116ac280c454f59332ee35c8c4f +DAUC 3G: opc = 3803ef5363b947c6aaa225e58fae3934 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = ee6466bc96202c5a557abbeff8babf63 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 45b0f69ab04c000053f2a822f2b3e824 +DAUC vector [0]: ck = 3f8c7587fe8e4b233af676aede30ba3b +DAUC vector [0]: ik = a7466cc1e6b2a1337d49d3b66e95d7b4 +DAUC vector [0]: res = 16c8233f05a0ac280000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = df75bc5ea899879f +DAUC vector [0]: sres = 13688f17 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_5: SUCCESS + + +===== test_set_6 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 2d609d4db0ac5bf0d2c0de267014de0d +DAUC 3G: opc = c35a0ab0bcbfc9252caff15f24efbde0 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 194aa756013896b74b4a2a3b0af4539e +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 7e6455f34cd300004a2a9f2f3a529b8c +DAUC vector [0]: ck = 4cd0846020f8fa0731dd47cbdc6be411 +DAUC vector [0]: ik = 88ab80a415f15c73711254a1d388f696 +DAUC vector [0]: res = 8c25a16cd918a1df0000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 84b417ae3aeab4f3 +DAUC vector [0]: sres = 553d00b3 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_6: SUCCESS + + +===== test_set_7 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = a530a7fe428fad1082c45eddfce13884 +DAUC 3G: opc = 27953e49bc8af6dcc6e730eb80286be3 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 3a4c2b3245c50eb5c71d08639395764d +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 88196c47984f00000a50c5f4056ccb68 +DAUC vector [0]: ck = 10f05bab75a99a5fbb98a9c287679c3b +DAUC vector [0]: ik = f9ec0865eb32f22369cade40c59c3a44 +DAUC vector [0]: res = a63241e1ffc3e5ab0000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 3b4e244cdc60ce03 +DAUC vector [0]: sres = 59f1a44a +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_7: SUCCESS + + +===== test_set_8 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = d9151cf04896e25830bf2e08267b8360 +DAUC 3G: opc = c4c93effe8a08138c203d4c27ce4e3d9 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = f761e5e93d603feb730e27556cb8a2ca +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 82a0f5287a5100006d6c0ff132426479 +DAUC vector [0]: ck = 71236b7129f9b22ab77ea7a54c96da22 +DAUC vector [0]: ik = 90527ebaa5588968db41727325a04d9e +DAUC vector [0]: res = 4a90b2171ac83a760000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 8d4ec01de597acfe +DAUC vector [0]: sres = 50588861 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_8: SUCCESS + + +===== test_set_9 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = a0e2971b6822e8d354a18cc235624ecb +DAUC 3G: opc = 82a26f22bba9e9488f949a10d98e9cc4 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 08eff828b13fdb562722c65c7f30a9b2 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = a2f858aa9e7d00001c14f5fcd445bc46 +DAUC vector [0]: ck = 08cef6d004ec61471a3c3cda048137fa +DAUC vector [0]: ik = ed0318ca5deb9206272f6e8fa64ba411 +DAUC vector [0]: res = 4bc2212d8624910a0000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = d8debc4ffbcd60aa +DAUC vector [0]: sres = cde6b027 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_9: SUCCESS + + +===== test_set_10 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 0da6f7ba86d5eac8a19cf563ac58642d +DAUC 3G: opc = 0db1071f8767562ca43a0a64c41e8d08 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 679ac4dbacd7d233ff9d6806f4149ce3 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 4c539a26e1da000071cc0b769fd1aa96 +DAUC vector [0]: ck = 69b1cae7c7429d975e245cacb05a517c +DAUC vector [0]: ik = 74f24e8c26df58e1b38d7dcd4f1b7fbd +DAUC vector [0]: res = 6fc30fee6d1235230000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = f0eaa50a1edcebb7 +DAUC vector [0]: sres = 02d13acd +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_10: SUCCESS + + +===== test_set_11 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 77b45843c88e58c10d202684515ed430 +DAUC 3G: opc = d483afae562409a326b5bb0b20c4d762 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 4c47eb3076dc55fe5106cb2034b8cd78 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 30ff25cdadd60000e08a00f7ed54d6fe +DAUC vector [0]: ck = 908c43f0569cb8f74bc971e706c36c5f +DAUC vector [0]: ik = c251df0d888dd9329bcf46655b226e40 +DAUC vector [0]: res = aefa357beac2a87a0000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 82dbab7f83f063da +DAUC vector [0]: sres = 44389d01 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_11: SUCCESS + + +===== test_set_12 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 729b17729270dd87ccdf1bfe29b4e9bb +DAUC 3G: opc = 228c2f2f06ac3268a9e616ee16db4ba1 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 311c4c929744d675b720f3b7e9b1cbd0 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 5380d158cfc30000f4e1436e9f67e4b2 +DAUC vector [0]: ck = 44c0f23c5493cfd241e48f197e1d1012 +DAUC vector [0]: ik = 0c9fb81613884c2535dd0eabf3b440d8 +DAUC vector [0]: res = 98dbbd099b3b408d0000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 3c66cb98cab2d33d +DAUC vector [0]: sres = 03e0fd84 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_12: SUCCESS + + +===== test_set_13 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = d32dd23e89dc662354ca12eb79dd32fa +DAUC 3G: opc = d22a4b4180a5325708a5ff70d9f67ec7 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = cf7d0ab1d94306950bf12018fbd46887 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 217af492728d00003bd338249751de80 +DAUC vector [0]: ck = 5af86b80edb70df5292cc1121cbad50c +DAUC vector [0]: ik = 7f4d6ae7440e18789a8b75ad3f42f03a +DAUC vector [0]: res = af4a411e1139f2c20000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 9612b5d88a4130bb +DAUC vector [0]: sres = be73b3dc +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_13: SUCCESS + + +===== test_set_14 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = af7c65e1927221de591187a2c5987a53 +DAUC 3G: opc = a4cf5c8155c08a7eff418e5443b98e55 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 1f0f8578464fd59b64bed2d09436b57a +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 837fd7b744390000557a836fd534e542 +DAUC vector [0]: ck = 3f8c3f3ccf7625bf77fc94bcfd22fd26 +DAUC vector [0]: ik = abcbae8fd46115e9961a55d0da5f2078 +DAUC vector [0]: res = 7bffa5c2f41fbc050000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 75a150df3c6aed08 +DAUC vector [0]: sres = 8fe019c7 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_14: SUCCESS + + +===== test_set_15 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 5bd7ecd3d3127a41d12539bed4e7cf71 +DAUC 3G: opc = 76089d3c0ff3efdc6e36721d4fceb747 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 59b75f14251c75031d0bcbac1c2c04c7 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 5be11495527d0000298064f82a439924 +DAUC vector [0]: ck = d42b2d615e49a03ac275a5aef97af892 +DAUC vector [0]: ik = 0b3f8d024fe6bfafaa982b8f82e319c2 +DAUC vector [0]: res = 7e3f44c7591f6f450000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = b7f92e426a36fec5 +DAUC vector [0]: sres = 27202b82 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_15: SUCCESS + + +===== test_set_16 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 6cd1c6ceb1e01e14f1b82316a90b7f3d +DAUC 3G: opc = a219dc37f1dc7d66738b5843c799f206 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = f69b78f300a0568bce9f0cb93c4be4c9 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 1c408a858b1e0000e6e96310f83b5689 +DAUC vector [0]: ck = 6edaf99e5bd9f85d5f36d91c1272fb4b +DAUC vector [0]: ik = d61c853c280dd9c46f297baec386de17 +DAUC vector [0]: res = 70f6bdb9ad21525f0000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 88d9de10a22004c5 +DAUC vector [0]: sres = ddd7efe6 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_16: SUCCESS + + +===== test_set_17 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = b73a90cbcf3afb622dba83c58a8415df +DAUC 3G: opc = df0c67868fa25f748b7044c6e7c245b8 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = b120f1c1a0102a2f507dd543de68281f +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = aefdaa5dddb90000c4741d698b7a7ed3 +DAUC vector [0]: ck = 66195dbed0313274c5ca7766615fa25e +DAUC vector [0]: ik = 66bec707eb2afc476d7408a8f2927b36 +DAUC vector [0]: res = 479dd25c20792d630000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = a819e577a8d6175b +DAUC vector [0]: sres = 67e4ff3f +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_17: SUCCESS + + +===== test_set_18 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 5122250214c33e723a5dd523fc145fc0 +DAUC 3G: opc = 981d464c7c52eb6e5036234984ad0bcf +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 81e92b6c0ee0e12ebceba8d92a99dfa5 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = ada15aeb7b980000a99729b59d5688b2 +DAUC vector [0]: ck = 5349fbe098649f948f5d2e973a81c00f +DAUC vector [0]: ik = 9744871ad32bf9bbd1dd5ce54e3e2e5a +DAUC vector [0]: res = 28d7b0f2a2ec3de50000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = 9a8d0e883ff0887a +DAUC vector [0]: sres = 8a3b8d17 +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_18: SUCCESS + + +===== test_set_19 +aud3g.u.umts.sqn == 31 +DAUC Computing 1 auth vector: 3G only (2G derived from 3G keys) +DAUC 3G: k = 90dca4eda45b53cf0f12d7c9c3bc6a89 +DAUC 3G: opc = cb9cccc4b9258e6dca4760379fb82581 +DAUC 3G: for sqn ind 0, previous sqn was 31 +DAUC vector [0]: rand = 9fddc72092c6ad036b6e464789315b78 +DAUC vector [0]: sqn = 32 +DAUC vector [0]: autn = 83cfd54db9330000695685b2b9214472 +DAUC vector [0]: ck = b5f2da03883b69f96bf52e029ed9ac45 +DAUC vector [0]: ik = b4721368bc16ea67875c5598688bb0ef +DAUC vector [0]: res = a95100e2760952cd0000000000000000 +DAUC vector [0]: res_len = 8 +DAUC vector [0]: kc = ed29b2f1c27f9f34 +DAUC vector [0]: sres = df58522f +DAUC vector [0]: auth_types = 0x3 +rc == 1 +aud3g.u.umts.sqn == 32 +vector matches expectations +===== test_set_19: SUCCESS + diff --git a/tests/auc/auc_ts_55_205_test_sets.ok b/tests/auc/auc_ts_55_205_test_sets.ok new file mode 100644 index 0000000..4eed389 --- /dev/null +++ b/tests/auc/auc_ts_55_205_test_sets.ok @@ -0,0 +1,2 @@ +3GPP TS 55.205 Test Sets +Done diff --git a/tests/auc/gen_ts_55_205_test_sets/Makefile.am b/tests/auc/gen_ts_55_205_test_sets/Makefile.am new file mode 100644 index 0000000..3225384 --- /dev/null +++ b/tests/auc/gen_ts_55_205_test_sets/Makefile.am @@ -0,0 +1,6 @@ +EXTRA_DIST = \ + func_template.c \ + main_template.c \ + pdftxt_2_c.py \ + ts55_205_test_sets.txt \ + $(NULL) diff --git a/tests/auc/gen_ts_55_205_test_sets/func_template.c b/tests/auc/gen_ts_55_205_test_sets/func_template.c new file mode 100644 index 0000000..0865432 --- /dev/null +++ b/tests/auc/gen_ts_55_205_test_sets/func_template.c @@ -0,0 +1,66 @@ +/* gen_ts_55_205_test_sets/func_template.c: Template to generate test code + * from 3GPP TS 55.205 test sets */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License + * along with this program. If not, see . + * + */ + +static void {func_name}(void) +{{ + struct osmo_sub_auth_data aud2g; + struct osmo_sub_auth_data aud3g; + struct osmo_auth_vector vec; + int rc; + + comment_start(); + + aud2g = (struct osmo_sub_auth_data){{ 0 }}; + + aud3g = (struct osmo_sub_auth_data){{ + .type = OSMO_AUTH_TYPE_UMTS, + .algo = OSMO_AUTH_ALG_MILENAGE, + .u.umts.sqn = 31, + }}; + + osmo_hexparse("{Ki}", + aud3g.u.umts.k, sizeof(aud3g.u.umts.k)); + osmo_hexparse("{OPc}", + aud3g.u.umts.opc, sizeof(aud3g.u.umts.opc)); + + osmo_hexparse("{RAND}", + fake_rand, sizeof(fake_rand)); + + vec = (struct osmo_auth_vector){{ {{0}} }}; + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 31, "%"PRIu64); + rc = auc_compute_vectors(&vec, 1, &aud2g, &aud3g, NULL, NULL); + VERBOSE_ASSERT(rc, == 1, "%d"); + VERBOSE_ASSERT(aud3g.u.umts.sqn, == 32, "%"PRIu64); + + VEC_IS(&vec, + " rand: {RAND}\n" + " ck: {MIL3G-CK}\n" + " ik: {MIL3G-IK}\n" + " res: {MIL3G-RES}0000000000000000\n" + " kc: {Kc}\n" + " sres: {SRES#1}\n" + ); + + comment_end(); +}} diff --git a/tests/auc/gen_ts_55_205_test_sets/main_template.c b/tests/auc/gen_ts_55_205_test_sets/main_template.c new file mode 100644 index 0000000..37f47c3 --- /dev/null +++ b/tests/auc/gen_ts_55_205_test_sets/main_template.c @@ -0,0 +1,116 @@ +/* gen_ts_55_205_test_sets/main_template.c: Template to generate test code + * from 3GPP TS 55.205 test sets */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include + +#include + +#include "logging.h" +#include "auc.h" + +#define comment_start() fprintf(stderr, "\n===== %s\n", __func__); +#define comment_end() fprintf(stderr, "===== %s: SUCCESS\n\n", __func__); + +#define VERBOSE_ASSERT(val, expect_op, fmt) \ + do { \ + fprintf(stderr, #val " == " fmt "\n", (val)); \ + OSMO_ASSERT((val) expect_op); \ + } while (0); + +char *vec_str(const struct osmo_auth_vector *vec) +{ + static char buf[1024]; + char *pos = buf; + char *end = buf + sizeof(buf); + +#define append(what) \ + if (pos >= end) \ + return buf; \ + pos += snprintf(pos, sizeof(buf) - (pos - buf), \ + " " #what ": %s\n", \ + osmo_hexdump_nospc((void*)&vec->what, sizeof(vec->what))) + + append(rand); + append(ck); + append(ik); + append(res); + append(kc); + append(sres); +#undef append + + return buf; +} + +#define VEC_IS(vec, expect) do { \ + char *_is = vec_str(vec); \ + if (strcmp(_is, expect)) { \ + fprintf(stderr, "MISMATCH! expected ==\n%s\n", \ + expect); \ + char *a = _is; \ + char *b = expect; \ + for (; *a && *b; a++, b++) { \ + if (*a != *b) { \ + while (a > _is && *(a-1) != '\n') a--; \ + fprintf(stderr, "mismatch at %d:\n" \ + "%s", (int)(a - _is), a); \ + break; \ + } \ + } \ + OSMO_ASSERT(false); \ + } else \ + fprintf(stderr, "vector matches expectations\n"); \ + } while (0) + +uint8_t fake_rand[16] = { 0 }; + +int rand_get(uint8_t *rand, unsigned int len) +{ + OSMO_ASSERT(len <= sizeof(fake_rand)); + memcpy(rand, fake_rand, len); + return len; +} + +FUNCTIONS + +int main() +{ + printf("3GPP TS 55.205 Test Sets\n"); + osmo_init_logging(&hlr_log_info); + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_timestamp(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_parse_category_mask(osmo_stderr_target, "DMAIN,1:DDB,1:DAUC,1"); + +FUNCTION_CALLS + + printf("Done\n"); + return 0; +} diff --git a/tests/auc/gen_ts_55_205_test_sets/pdftxt_2_c.py b/tests/auc/gen_ts_55_205_test_sets/pdftxt_2_c.py new file mode 100755 index 0000000..b01a797 --- /dev/null +++ b/tests/auc/gen_ts_55_205_test_sets/pdftxt_2_c.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# FIXME: use python3 once buildslaves are updated. +# Convert test sets pasted from 3GPP TS 55.205 to C code. + +# (C) 2016 by sysmocom s.f.m.c. GmbH +# +# All Rights Reserved +# +# Author: Neels Hofmeyr +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import sys, os + +script_dir = sys.path[0] + +fields = ( + 'Ki', + 'RAND', + 'OP', + 'OPc', + 'MIL3G-RES', + 'SRES#1', + 'SRES#2', + 'MIL3G-CK', + 'MIL3G-IK', + 'Kc', +) + +test_sets_lines = [] +test_set_lines = None + +for line in [l.strip() for l in open(os.path.join(script_dir, 'ts55_205_test_sets.txt'), 'r')]: + if line.startswith('Test Set'): + if test_set_lines: + test_sets_lines.append(test_set_lines) + test_set_lines = [] + elif len(line) == 8: + try: + is_hex = int(line, 16) + test_set_lines.append(line) + except ValueError: + pass + +if test_set_lines: + test_sets_lines.append(test_set_lines) + +# Magic fixups for PDF-to-text uselessness +idx = (( 0, 10, 15, 19), + ( 1, 11, 16, 20), + ( 2, 12, 17, 21), + ( 3, 13, 18, 22), + ( 4, 14), + ( 5, ), + ( 6, ), + ( 7, 23, 26, 28), + ( 8, 24, 27, 29), + ( 9, 25 ), + ) + +test_sets = [] +for l in test_sets_lines: + test_sets.append( [ ''.join([l[i] for i in li]) for li in idx ] ) + +func_templ = open(os.path.join(script_dir, 'func_template.c'), 'r').read() + +funcs = [] +func_calls = [] +nr = 0 +for test_set in test_sets: + nr += 1 + func_name = 'test_set_%d' % nr + kwargs = dict(zip(fields, test_set)) + kwargs['func_name'] = func_name + + func_calls.append('\t%s();' % func_name) + funcs.append(func_templ.format(**kwargs)) + +templ = open(os.path.join(script_dir, 'main_template.c')).read() + +code = templ.replace('FUNCTIONS', '\n'.join(funcs)).replace('FUNCTION_CALLS', '\n'.join(func_calls)) + +print(''' +/***** DO NOT EDIT THIS FILE -- THIS CODE IS GENERATED ***** + ***** by gen_ts_55_205_test_sets/pdftxt_2_c.py *****/ +''') +print(code) + diff --git a/tests/auc/gen_ts_55_205_test_sets/ts55_205_test_sets.txt b/tests/auc/gen_ts_55_205_test_sets/ts55_205_test_sets.txt new file mode 100644 index 0000000..0d4d14b --- /dev/null +++ b/tests/auc/gen_ts_55_205_test_sets/ts55_205_test_sets.txt @@ -0,0 +1,972 @@ + +Test Set 1 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +465b5ce8 +23553cbe +cdc202d5 +cd63cb71 +a54211d5 +46f8416a +a54211d5 +b40ba9a3 +f769bcd7 +eae4be82 + +b199b49f +9637a89d +123e20f6 +954a9f4e +e3ba50bf + +aa5f0a2e +218ae64d +2b6d676a +48a5994e + +e238a6bc +ae47bf35 +c72cb318 +37a02baf + +c58b2a05 +51044604 +3af9a08b + +bbf0d987 +12767271 + +b21bf8cb +1c6d3441 + +Test Set 2 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +fec86ba6 +9f7c8d02 +dbc59adc +1006020f +8011c48c +8c308a5e +8011c48c +5dbdbb29 +59a92d3b +aa01739b + +eb707ed0 +1accf4db +b6f9a0ef +0a478bf6 +0c214ed2 + +8905757b +213ccff0 +735477b7 +b699f15c + +1bb44b8f +c7f71a6a +fadf8374 +062e42b3 + +54e8f3cd +476a0443 +8caa976d + +e665b046 +487055cf + +179a5098 +88b2307b + +ETSI + + 3GPP TS 55.205 version 6.2.0 Release 6 + +10 + +ETSI TS 155 205 V6.2.0 (2006-03) + +Test Set 3 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +9e5944ae +ce83dbc5 +223014c5 +a64a507a +f365cd68 +cfbce3fe +f365cd68 +e203edb3 +0c4524ad +9a8ec95f + +a94b8116 +4ac0274a +806694c0 +e1a2a98b +3cd92e96 + +5c82fbf9 +157c17f8 +07ca1eee +b88eb421 + +f32db751 +0d017bd6 +f57f004f +0135dc87 + +971574f5 +eac041c4 +408cc507 + +a94b0d61 +dd830d20 + +b816345d +854fc46b + +Test Set 4 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +4ab1deb0 +74b0cd60 +2d16c5cd +dcf07cbd +5860fc1b +9655e265 +5860fc1b +7657766b +1c42e960 +cdc1dc08 + +5ca6ceb0 +31a1c833 +1fdf6b22 +51855290 +ce351e7e + +51fc98e7 +9b2b6ce2 +383584e3 +b92a07a9 + +7d026a84 +b8c4a186 +bef2a8d8 +891e523e + +373d1c21 +d89b8fa9 +41b81a22 + +38f307e3 +9f2744e0 + +de9242f9 +708ccb53 + +Test Set 5 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +6c38a116 +ee6466bc +1ba00a1a +3803ef53 +16c8233f +13688f17 +16c8233f +3f8c7587 +a7466cc1 +df75bc5e + +ac280c45 +96202c5a +7c6700ac +63b947c6 +05a0ac28 + +4f59332e +557abbef +8c3ff3e9 +aaa225e5 + +e35c8c4f +f8babf63 +6ad08725 +8fae3934 + +fe8e4b23 +e6b2a133 +a899879f + +3af676ae +7d49d3b6 + +de30ba3b +6e95d7b4 + +Test Set 6 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +2d609d4d +194aa756 +460a4838 +c35a0ab0 +8c25a16c +553d00b3 +8c25a16c +4cd08460 +88ab80a4 +84b417ae + +b0ac5bf0 +013896b7 +5427aa39 +bcbfc925 +d918a1df + +d2c0de26 +4b4a2a3b +264aac8e +2caff15f + +7014de0d +0af4539e +fc9e73e8 +24efbde0 + +20f8fa07 +15f15c73 +3aeab4f3 + +31dd47cb +711254a1 + +dc6be411 +d388f696 + +ETSI + + 3GPP TS 55.205 version 6.2.0 Release 6 + +11 + +ETSI TS 155 205 V6.2.0 (2006-03) + +Test Set 7 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +a530a7fe +3a4c2b32 +511c6c4e +27953e49 +a63241e1 +59f1a44a +a63241e1 +10f05bab +f9ec0865 +3b4e244c + +428fad10 +45c50eb5 +83e38c89 +bc8af6dc +ffc3e5ab + +82c45edd +c71d0863 +b1c5d8dd +c6e730eb + +fce13884 +9395764d +e62426fa +80286be3 + +75a99a5f +eb32f223 +dc60ce03 + +bb98a9c2 +69cade40 + +87679c3b +c59c3a44 + +Test Set 8 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +d9151cf0 +f761e5e9 +75fc2233 +c4c93eff +4a90b217 +50588861 +4a90b217 +71236b71 +90527eba +8d4ec01d + +4896e258 +3d603feb +a44294ee +e8a08138 +1ac83a76 + +30bf2e08 +730e2755 +8e6de25c +c203d4c2 + +267b8360 +6cb8a2ca +4353d26b +7ce4e3d9 + +29f9b22a +a5588968 +e597acfe + +b77ea7a5 +db417273 + +4c96da22 +25a04d9e + +Test Set 9 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +a0e2971b +08eff828 +323792fa +82a26f22 +4bc2212d +cde6b027 +4bc2212d +08cef6d0 +ed0318ca +d8debc4f + +6822e8d3 +b13fdb56 +ca21fb4d +bba9e948 +8624910a + +54a18cc2 +2722c65c +5d6f13c1 +8f949a10 + +35624ecb +7f30a9b2 +45a9d2c1 +d98e9cc4 + +04ec6147 +5deb9206 +fbcd60aa + +1a3c3cda +272f6e8f + +048137fa +a64ba411 + +Test Set 10 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +0da6f7ba +679ac4db +4b9a26fa +0db1071f +6fc30fee +02d13acd +6fc30fee +69b1cae7 +74f24e8c +f0eaa50a + +86d5eac8 +acd7d233 +459e3acb +8767562c +6d123523 + +a19cf563 +ff9d6806 +ff36f401 +a43a0a64 + +ac58642d +f4149ce3 +5de3bdc1 +c41e8d08 + +c7429d97 +26df58e1 +1edcebb7 + +5e245cac +b38d7dcd + +b05a517c +4f1b7fbd + +ETSI + + 3GPP TS 55.205 version 6.2.0 Release 6 + +12 + +ETSI TS 155 205 V6.2.0 (2006-03) + +Test Set 11 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +77b45843 +4c47eb30 +bf3286c7 +d483afae +aefa357b +44389d01 +aefa357b +908c43f0 +c251df0d +82dbab7f + +c88e58c1 +76dc55fe +a51409ce +562409a3 +eac2a87a + +0d202684 +5106cb20 +95724d50 +26b5bb0b + +515ed430 +34b8cd78 +3bfe6e70 +20c4d762 + +569cb8f7 +888dd932 +83f063da + +4bc971e7 +9bcf4665 + +06c36c5f +5b226e40 + +Test Set 12 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +729b1772 +311c4c92 +d04c9c35 +228c2f2f +98dbbd09 +03e0fd84 +98dbbd09 +44c0f23c +0c9fb816 +3c66cb98 + +9270dd87 +9744d675 +bd2262fa +06ac3268 +9b3b408d + +ccdf1bfe +b720f3b7 +810d2924 +a9e616ee + +29b4e9bb +e9b1cbd0 +d036fd13 +16db4ba1 + +5493cfd2 +13884c25 +cab2d33d + +41e48f19 +35dd0eab + +7e1d1012 +f3b440d8 + +Test Set 13 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +d32dd23e +cf7d0ab1 +fe75905b +d22a4b41 +af4a411e +be73b3dc +af4a411e +5af86b80 +7f4d6ae7 +9612b5d8 + +89dc6623 +d9430695 +9da47d35 +80a53257 +1139f2c2 + +54ca12eb +0bf12018 +6236d031 +08a5ff70 + +79dd32fa +fbd46887 +4e09c32e +d9f67ec7 + +edb70df5 +440e1878 +8a4130bb + +292cc112 +9a8b75ad + +1cbad50c +3f42f03a + +Test Set 14 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +af7c65e1 +1f0f8578 +0c7acb8d +a4cf5c81 +7bffa5c2 +8fe019c7 +7bffa5c2 +3f8c3f3c +abcbae8f +75a150df + +927221de +464fd59b +95b7d4a3 +55c08a7e +f41fbc05 + +591187a2 +64bed2d0 +1c5aca6d +ff418e54 + +c5987a53 +9436b57a +26345a88 +43b98e55 + +cf7625bf +d46115e9 +3c6aed08 + +77fc94bc +961a55d0 + +fd22fd26 +da5f2078 + +ETSI + + 3GPP TS 55.205 version 6.2.0 Release 6 + +13 + +ETSI TS 155 205 V6.2.0 (2006-03) + +Test Set 15 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +5bd7ecd3 +59b75f14 +f967f760 +76089d3c +7e3f44c7 +27202b82 +7e3f44c7 +d42b2d61 +0b3f8d02 +b7f92e42 + +d3127a41 +251c7503 +38b920a9 +0ff3efdc +591f6f45 + +d12539be +1d0bcbac +cd25e10c +6e36721d + +d4e7cf71 +1c2c04c7 +08b49924 +4fceb747 + +5e49a03a +4fe6bfaf +6a36fec5 + +c275a5ae +aa982b8f + +f97af892 +82e319c2 + +Test Set 16 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +6cd1c6ce +f69b78f3 +078bfca9 +a219dc37 +70f6bdb9 +ddd7efe6 +70f6bdb9 +6edaf99e +d61c853c +88d9de10 + +b1e01e14 +00a0568b +564659ec +f1dc7d66 +ad21525f + +f1b82316 +ce9f0cb9 +d8851e84 +738b5843 + +a90b7f3d +3c4be4c9 +e6c59b48 +c799f206 + +5bd9f85d +280dd9c4 +a22004c5 + +5f36d91c +6f297bae + +1272fb4b +c386de17 + +Test Set 17 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +b73a90cb +b120f1c1 +b672047e +df0c6786 +479dd25c +67e4ff3f +479dd25c +66195dbe +66bec707 +a819e577 + +cf3afb62 +a0102a2f +003bb952 +8fa25f74 +20792d63 + +2dba83c5 +507dd543 +dca6cb8a +8b7044c6 + +8a8415df +de68281f +f0e5b779 +e7c245b8 + +d0313274 +eb2afc47 +a8d6175b + +c5ca7766 +6d7408a8 + +615fa25e +f2927b36 + +Test Set 18 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +51222502 +81e92b6c +c9e87632 +981d464c +28d7b0f2 +8a3b8d17 +28d7b0f2 +5349fbe0 +9744871a +9a8d0e88 + +14c33e72 +0ee0e12e +86b5b9ff +7c52eb6e +a2ec3de5 + +3a5dd523 +bceba8d9 +bdf56e12 +50362349 + +fc145fc0 +2a99dfa5 +97d0887b +84ad0bcf + +98649f94 +d32bf9bb +3ff0887a + +8f5d2e97 +d1dd5ce5 + +3a81c00f +4e3e2e5a + +ETSI + + 3GPP TS 55.205 version 6.2.0 Release 6 + +Test Set 19 +Ki +RAND +OP +OPc +MIL3G-RES +SRES#1 +SRES#2 +MIL3G-CK +MIL3G-IK +Kc + +90dca4ed +9fddc720 +3ffcfe5b +cb9cccc4 +a95100e2 +df58522f +a95100e2 +b5f2da03 +b4721368 +ed29b2f1 + +14 + +ETSI TS 155 205 V6.2.0 (2006-03) + +a45b53cf +92c6ad03 +7b111158 +b9258e6d +760952cd + +0f12d7c9 +6b6e4647 +9920d352 +ca476037 + +c3bc6a89 +89315b78 +8e84e655 +9fb82581 + +883b69f9 +bc16ea67 +c27f9f34 + +6bf52e02 +875c5598 + +9ed9ac45 +688bb0ef + diff --git a/tests/db/Makefile.am b/tests/db/Makefile.am new file mode 100644 index 0000000..55b1655 --- /dev/null +++ b/tests/db/Makefile.am @@ -0,0 +1,48 @@ +AM_CFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/src \ + -I$(top_builddir)/src \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(SQLITE3_CFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + db_test.ok \ + db_test.err \ + $(NULL) + +check_PROGRAMS = db_test + +db_test_SOURCES = \ + db_test.c \ + $(NULL) + +db_test_LDADD = \ + $(top_srcdir)/src/db.c \ + $(top_srcdir)/src/db_hlr.c \ + $(top_srcdir)/src/db_auc.c \ + $(top_srcdir)/src/logging.c \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(SQLITE3_LIBS) \ + $(NULL) + +.PHONY: db_test.db update_exp manual manual-nonverbose manual-gdb +db_test.db: + rm -f db_test.db + sqlite3 $(builddir)/db_test.db < $(top_srcdir)/sql/hlr.sql + +update_exp: db_test.db + cd $(builddir); ./db_test >"$(srcdir)/db_test.ok" 2>"$(srcdir)/db_test.err" + +manual: db_test.db + cd $(builddir); ./db_test -v + +manual-nonverbose: db_test.db + cd $(builddir); ./db_test + +manual-gdb: db_test.db + cd $(builddir); gdb -ex run --args ./db_test -v diff --git a/tests/db/db_test.c b/tests/db/db_test.c new file mode 100644 index 0000000..389ed00 --- /dev/null +++ b/tests/db/db_test.c @@ -0,0 +1,846 @@ +/* (C) 2017 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "db.h" +#include "logging.h" + +#define comment_start() fprintf(stderr, "\n===== %s\n", __func__); +#define comment(fmt, args...) fprintf(stderr, "\n--- " fmt "\n\n", ## args); +#define comment_end() fprintf(stderr, "===== %s: SUCCESS\n\n", __func__); + +#define fill_invalid(x) _fill_invalid(&x, sizeof(x)) +static void _fill_invalid(void *dest, size_t size) +{ + uint8_t *pos = dest; + size_t remain = size; + int wrote = 0; + do { + remain -= wrote; + pos += wrote; + wrote = snprintf((void*)pos, remain, "-invalid-data"); + } while (wrote < remain); +} + +/* Perform a function call and verbosely assert that its return value is as expected. + * The return code is then available in g_rc. */ +#define ASSERT_RC(call, expect_rc) \ + do { \ + fprintf(stderr, #call " --> " #expect_rc "\n"); \ + g_rc = call; \ + if (g_rc != (expect_rc)) \ + fprintf(stderr, " MISMATCH: got rc = %d, expected: " \ + #expect_rc " = %d\n", g_rc, expect_rc); \ + OSMO_ASSERT(g_rc == (expect_rc)); \ + fprintf(stderr, "\n"); \ + } while (0) + +/* Do db_subscr_get_by_xxxx and verbosely assert that its return value is as expected. + * Print the subscriber struct to stderr to be validated by db_test.err. + * The result is then available in g_subscr. */ +#define ASSERT_SEL(by, val, expect_rc) \ + do { \ + int rc; \ + fill_invalid(g_subscr); \ + fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> " \ + #expect_rc "\n"); \ + rc = db_subscr_get_by_##by(dbc, val, &g_subscr); \ + if (rc != (expect_rc)) \ + fprintf(stderr, " MISMATCH: got rc = %d, expected: " \ + #expect_rc " = %d\n", rc, expect_rc); \ + OSMO_ASSERT(rc == (expect_rc)); \ + if (!rc) \ + dump_subscr(&g_subscr); \ + fprintf(stderr, "\n"); \ + } while (0) + +/* Do db_get_auth_data() and verbosely assert that its return value is as expected. + * Print the subscriber struct to stderr to be validated by db_test.err. + * The results are then available in g_aud2g and g_aud3g. */ +#define ASSERT_SEL_AUD(imsi, expect_rc, expect_id) \ + do { \ + fill_invalid(g_aud2g); \ + fill_invalid(g_aud3g); \ + g_id = 0; \ + ASSERT_RC(db_get_auth_data(dbc, imsi, &g_aud2g, &g_aud3g, &g_id), expect_rc); \ + if (!g_rc) { \ + dump_aud("2G", &g_aud2g); \ + dump_aud("3G", &g_aud3g); \ + }\ + if (g_id != expect_id) {\ + fprintf(stderr, "MISMATCH: got subscriber id %"PRId64 \ + ", expected %"PRId64"\n", g_id, (int64_t)(expect_id)); \ + OSMO_ASSERT(g_id == expect_id); \ + } \ + fprintf(stderr, "\n"); \ + } while (0) + +static struct db_context *dbc = NULL; +static void *ctx = NULL; +static struct hlr_subscriber g_subscr; +static struct osmo_sub_auth_data g_aud2g; +static struct osmo_sub_auth_data g_aud3g; +static int g_rc; +static int64_t g_id; + +#define Pfv(name, fmt, val) \ + fprintf(stderr, " ." #name " = " fmt ",\n", val) +#define Pfo(name, fmt, obj) \ + Pfv(name, fmt, obj->name) + +/* Print a subscriber struct to stderr to be validated by db_test.err. */ +void dump_subscr(struct hlr_subscriber *subscr) +{ +#define Ps(name) \ + if (*subscr->name) \ + Pfo(name, "'%s'", subscr) +#define Pd(name) \ + Pfv(name, "%"PRId64, (int64_t)subscr->name) +#define Pd_nonzero(name) \ + if (subscr->name) \ + Pd(name) +#define Pb(if_val, name) \ + if (subscr->name == (if_val)) \ + Pfv(name, "%s", subscr->name ? "true" : "false") + + fprintf(stderr, "struct hlr_subscriber {\n"); + Pd(id); + Ps(imsi); + Ps(msisdn); + Ps(vlr_number); + Ps(sgsn_number); + Ps(sgsn_address); + Pd_nonzero(periodic_lu_timer); + Pd_nonzero(periodic_rau_tau_timer); + Pb(false, nam_cs); + Pb(false, nam_ps); + if (subscr->lmsi) + Pfo(lmsi, "0x%x", subscr); + Pb(true, ms_purged_cs); + Pb(true, ms_purged_ps); + fprintf(stderr, "}\n"); +#undef Ps +#undef Pd +#undef Pd_nonzero +#undef Pb +} + +void dump_aud(const char *label, struct osmo_sub_auth_data *aud) +{ + if (aud->type == OSMO_AUTH_TYPE_NONE) { + fprintf(stderr, "%s: none\n", label); + return; + } + + fprintf(stderr, "%s: struct osmo_sub_auth_data {\n", label); +#define Pf(name, fmt) \ + Pfo(name, fmt, aud) +#define Phex(name) \ + Pfv(name, "'%s'", osmo_hexdump_nospc(aud->name, sizeof(aud->name))) + + Pfv(type, "%s", osmo_sub_auth_type_name(aud->type)); + Pfv(algo, "%s", osmo_auth_alg_name(aud->algo)); + switch (aud->type) { + case OSMO_AUTH_TYPE_GSM: + Phex(u.gsm.ki); + break; + case OSMO_AUTH_TYPE_UMTS: + Phex(u.umts.opc); + Pf(u.umts.opc_is_op, "%u"); + Phex(u.umts.k); + Phex(u.umts.amf); + if (aud->u.umts.sqn) { + Pf(u.umts.sqn, "%"PRIu64); + Pf(u.umts.sqn, "0x%"PRIx64); + } + if (aud->u.umts.ind_bitlen) + Pf(u.umts.ind_bitlen, "%u"); + break; + default: + OSMO_ASSERT(false); + } + + fprintf(stderr, "}\n"); + +#undef Pf +#undef Phex +} + +static const char *imsi0 = "123456789000000"; +static const char *imsi1 = "123456789000001"; +static const char *imsi2 = "123456789000002"; +static const char *short_imsi = "123456"; +static const char *unknown_imsi = "999999999"; + +static void test_subscr_create_update_sel_delete() +{ + int64_t id0, id1, id2, id_short; + comment_start(); + + comment("Create with valid / invalid IMSI"); + + ASSERT_RC(db_subscr_create(dbc, imsi0), 0); + ASSERT_SEL(imsi, imsi0, 0); + id0 = g_subscr.id; + ASSERT_RC(db_subscr_create(dbc, imsi1), 0); + ASSERT_SEL(imsi, imsi1, 0); + id1 = g_subscr.id; + ASSERT_RC(db_subscr_create(dbc, imsi2), 0); + ASSERT_SEL(imsi, imsi2, 0); + id2 = g_subscr.id; + ASSERT_RC(db_subscr_create(dbc, imsi0), -EIO); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_create(dbc, imsi1), -EIO); + ASSERT_RC(db_subscr_create(dbc, imsi1), -EIO); + ASSERT_SEL(imsi, imsi1, 0); + ASSERT_RC(db_subscr_create(dbc, imsi2), -EIO); + ASSERT_RC(db_subscr_create(dbc, imsi2), -EIO); + ASSERT_SEL(imsi, imsi2, 0); + + ASSERT_RC(db_subscr_create(dbc, "123456789 000003"), -EINVAL); + ASSERT_SEL(imsi, "123456789000003", -ENOENT); + + ASSERT_RC(db_subscr_create(dbc, "123456789000002123456"), -EINVAL); + ASSERT_SEL(imsi, "123456789000002123456", -ENOENT); + + ASSERT_RC(db_subscr_create(dbc, "foobar123"), -EINVAL); + ASSERT_SEL(imsi, "foobar123", -ENOENT); + + ASSERT_RC(db_subscr_create(dbc, "123"), -EINVAL); + ASSERT_SEL(imsi, "123", -ENOENT); + + ASSERT_RC(db_subscr_create(dbc, short_imsi), 0); + ASSERT_SEL(imsi, short_imsi, 0); + id_short = g_subscr.id; + + + comment("Set valid / invalid MSISDN"); + + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0, "54321"), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_SEL(msisdn, "54321", 0); + ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0, + "54321012345678912345678"), -EINVAL); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_SEL(msisdn, "54321", 0); + ASSERT_SEL(msisdn, "54321012345678912345678", -ENOENT); + ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0, + "543 21"), -EINVAL); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_SEL(msisdn, "543 21", -ENOENT); + ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0, + "foobar123"), -EINVAL); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_SEL(msisdn, "foobar123", -ENOENT); + ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0, + "5"), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_SEL(msisdn, "5", 0); + ASSERT_SEL(msisdn, "54321", -ENOENT); + ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0, + "543210123456789"), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_SEL(msisdn, "543210123456789", 0); + ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, imsi0, + "5432101234567891"), -EINVAL); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_SEL(msisdn, "5432101234567891", -ENOENT); + + comment("Set MSISDN on non-existent / invalid IMSI"); + + ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, unknown_imsi, "99"), -ENOENT); + ASSERT_SEL(msisdn, "99", -ENOENT); + + ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, "foobar", "99"), -ENOENT); + ASSERT_SEL(msisdn, "99", -ENOENT); + + comment("Set / unset nam_cs and nam_ps"); + + /* nam_val, is_ps */ + ASSERT_RC(db_subscr_nam(dbc, imsi0, false, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, false, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, true, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, true, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + + comment("Set / unset nam_cs and nam_ps *again*"); + ASSERT_RC(db_subscr_nam(dbc, imsi0, false, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, false, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, false, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, false, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, true, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, true, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, true, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_nam(dbc, imsi0, true, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + + comment("Set nam_cs and nam_ps on non-existent / invalid IMSI"); + + ASSERT_RC(db_subscr_nam(dbc, unknown_imsi, false, true), -ENOENT); + ASSERT_RC(db_subscr_nam(dbc, unknown_imsi, false, false), -ENOENT); + ASSERT_SEL(imsi, unknown_imsi, -ENOENT); + + ASSERT_RC(db_subscr_nam(dbc, "foobar", false, true), -ENOENT); + ASSERT_RC(db_subscr_nam(dbc, "foobar", false, false), -ENOENT); + + comment("Record LU for PS and CS (SGSN and VLR names)"); + + ASSERT_RC(db_subscr_lu(dbc, id0, "5952", true), 0); + ASSERT_SEL(id, id0, 0); + ASSERT_RC(db_subscr_lu(dbc, id0, "712", false), 0); + ASSERT_SEL(id, id0, 0); + + comment("Record LU for PS and CS (SGSN and VLR names) *again*"); + + ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0); + ASSERT_SEL(id, id0, 0); + ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0); + ASSERT_SEL(id, id0, 0); + ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0); + ASSERT_SEL(id, id0, 0); + ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0); + ASSERT_SEL(id, id0, 0); + + comment("Unset LU info for PS and CS (SGSN and VLR names)"); + ASSERT_RC(db_subscr_lu(dbc, id0, "", true), 0); + ASSERT_SEL(id, id0, 0); + ASSERT_RC(db_subscr_lu(dbc, id0, "", false), 0); + ASSERT_SEL(id, id0, 0); + + ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0); + ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0); + ASSERT_SEL(id, id0, 0); + ASSERT_RC(db_subscr_lu(dbc, id0, NULL, true), 0); + ASSERT_SEL(id, id0, 0); + ASSERT_RC(db_subscr_lu(dbc, id0, NULL, false), 0); + ASSERT_SEL(id, id0, 0); + + comment("Record LU for non-existent ID"); + ASSERT_RC(db_subscr_lu(dbc, 99999, "5952", true), -ENOENT); + ASSERT_RC(db_subscr_lu(dbc, 99999, "712", false), -ENOENT); + ASSERT_SEL(id, 99999, -ENOENT); + + comment("Purge and un-purge PS and CS"); + + /* purge_val, is_ps */ + ASSERT_RC(db_subscr_purge(dbc, imsi0, true, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, true, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, false, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, false, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + + comment("Purge PS and CS *again*"); + + ASSERT_RC(db_subscr_purge(dbc, imsi0, true, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, true, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, false, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, false, true), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, true, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, true, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, false, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_purge(dbc, imsi0, false, false), 0); + ASSERT_SEL(imsi, imsi0, 0); + + comment("Purge on non-existent / invalid IMSI"); + + ASSERT_RC(db_subscr_purge(dbc, unknown_imsi, true, true), -ENOENT); + ASSERT_SEL(imsi, unknown_imsi, -ENOENT); + ASSERT_RC(db_subscr_purge(dbc, unknown_imsi, true, false), -ENOENT); + ASSERT_SEL(imsi, unknown_imsi, -ENOENT); + + comment("Delete non-existent / invalid IDs"); + + ASSERT_RC(db_subscr_delete_by_id(dbc, 999), -ENOENT); + ASSERT_RC(db_subscr_delete_by_id(dbc, -10), -ENOENT); + + comment("Delete subscribers"); + + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_delete_by_id(dbc, id0), 0); + ASSERT_SEL(imsi, imsi0, -ENOENT); + ASSERT_RC(db_subscr_delete_by_id(dbc, id0), -ENOENT); + + ASSERT_SEL(imsi, imsi1, 0); + ASSERT_RC(db_subscr_delete_by_id(dbc, id1), 0); + ASSERT_SEL(imsi, imsi1, -ENOENT); + + ASSERT_SEL(imsi, imsi2, 0); + ASSERT_RC(db_subscr_delete_by_id(dbc, id2), 0); + ASSERT_SEL(imsi, imsi2, -ENOENT); + + ASSERT_SEL(imsi, short_imsi, 0); + ASSERT_RC(db_subscr_delete_by_id(dbc, id_short), 0); + ASSERT_SEL(imsi, short_imsi, -ENOENT); + + comment_end(); +} + +static const struct sub_auth_data_str *mk_aud_2g(enum osmo_auth_algo algo, + const char *ki) +{ + static struct sub_auth_data_str aud; + aud = (struct sub_auth_data_str){ + .type = OSMO_AUTH_TYPE_GSM, + .algo = algo, + .u.gsm.ki = ki, + }; + return &aud; +} + +static const struct sub_auth_data_str *mk_aud_3g(enum osmo_auth_algo algo, + const char *opc, bool opc_is_op, + const char *k, unsigned int ind_bitlen) +{ + static struct sub_auth_data_str aud; + aud = (struct sub_auth_data_str){ + .type = OSMO_AUTH_TYPE_UMTS, + .algo = algo, + .u.umts.k = k, + .u.umts.opc = opc, + .u.umts.opc_is_op = opc_is_op ? 1 : 0, + .u.umts.ind_bitlen = ind_bitlen, + }; + return &aud; +} + +static void test_subscr_aud() +{ + int64_t id; + + comment_start(); + + comment("Get auth data for non-existent subscriber"); + ASSERT_SEL_AUD(unknown_imsi, -ENOENT, 0); + + comment("Create subscriber"); + + ASSERT_RC(db_subscr_create(dbc, imsi0), 0); + ASSERT_SEL(imsi, imsi0, 0); + + id = g_subscr.id; + ASSERT_SEL_AUD(imsi0, -ENOENT, id); + + + comment("Set auth data, 2G only"); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + /* same again */ + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_COMP128v2, "BeadedBeeAced1EbbedDefacedFacade")), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_COMP128v3, "DeafBeddedBabeAcceededFadedDecaf")), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_XOR, "CededEffacedAceFacedBadFadedBeef")), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + comment("Remove 2G auth data"); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)), + 0); + ASSERT_SEL_AUD(imsi0, -ENOENT, id); + + /* Removing nothing results in -ENOENT */ + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)), + -ENOENT); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_XOR, "CededEffacedAceFacedBadFadedBeef")), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_NONE, "f000000000000f00000000000f000000")), + 0); + ASSERT_SEL_AUD(imsi0, -ENOENT, id); + + + comment("Set auth data, 3G only"); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "BeefedCafeFaceAcedAddedDecadeFee", true, + "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + /* same again */ + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "BeefedCafeFaceAcedAddedDecadeFee", true, + "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "Deaf0ff1ceD0d0DabbedD1ced1ceF00d", true, + "F1bbed0afD0eF0bD0ffed0ddF1fe0b0e", 0)), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "BeefedCafeFaceAcedAddedDecadeFee", false, + "DeafBeddedBabeAcceededFadedDecaf", + OSMO_MILENAGE_IND_BITLEN_MAX)), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "CededEffacedAceFacedBadFadedBeef", false, + "BeefedCafeFaceAcedAddedDecadeFee", 5)), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + comment("Remove 3G auth data"); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)), + 0); + ASSERT_SEL_AUD(imsi0, -ENOENT, id); + + /* Removing nothing results in -ENOENT */ + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)), + -ENOENT); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "CededEffacedAceFacedBadFadedBeef", false, + "BeefedCafeFaceAcedAddedDecadeFee", 5)), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_NONE, + "asdfasdfasd", false, + "asdfasdfasdf", 99999)), + 0); + ASSERT_SEL_AUD(imsi0, -ENOENT, id); + + + comment("Set auth data, 2G and 3G"); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_COMP128v3, "CededEffacedAceFacedBadFadedBeef")), + 0); + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "BeefedCafeFaceAcedAddedDecadeFee", false, + "DeafBeddedBabeAcceededFadedDecaf", 5)), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + + comment("Set invalid auth data"); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(99999, "f000000000000f00000000000f000000")), + -EINVAL); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_XOR, "f000000000000f00000000000f000000f00000000")), + -EINVAL); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_XOR, "f00")), + -EINVAL); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_2g(OSMO_AUTH_ALG_MILENAGE, "0123456789abcdef0123456789abcdef")), + -EINVAL); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "0f000000000000f00000000000f000000", false, + "f000000000000f00000000000f000000", 5)), + -EINVAL); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "f000000000000f00000000000f000000", false, + "000000000000f00000000000f000000", 5)), + -EINVAL); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "f000000000000f00000000000f000000", false, + "f000000000000f00000000000f000000", + OSMO_MILENAGE_IND_BITLEN_MAX + 1)), + -EINVAL); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "X000000000000f00000000000f000000", false, + "f000000000000f00000000000f000000", 5)), + -EINVAL); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "f000000000000f00000000000f000000", false, + "f000000000000 f00000000000 f000000", 5)), + -EINVAL); + ASSERT_SEL_AUD(imsi0, 0, id); + + comment("Delete subscriber"); + + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_delete_by_id(dbc, id), 0); + ASSERT_SEL(imsi, imsi0, -ENOENT); + + comment("Re-add subscriber and verify auth data didn't come back"); + + ASSERT_RC(db_subscr_create(dbc, imsi0), 0); + ASSERT_SEL(imsi, imsi0, 0); + + /* For this test to work, we want to get the same subscriber ID back, + * and make sure there are no auth data leftovers for this ID. */ + OSMO_ASSERT(id == g_subscr.id); + ASSERT_SEL_AUD(imsi0, -ENOENT, id); + + ASSERT_RC(db_subscr_delete_by_id(dbc, id), 0); + ASSERT_SEL(imsi, imsi0, -ENOENT); + + comment_end(); +} + +static void test_subscr_sqn() +{ + int64_t id; + + comment_start(); + + comment("Set SQN for unknown subscriber"); + + ASSERT_RC(db_update_sqn(dbc, 99, 999), -ENOENT); + ASSERT_SEL(id, 99, -ENOENT); + + ASSERT_RC(db_update_sqn(dbc, 9999, 99), -ENOENT); + ASSERT_SEL(id, 9999, -ENOENT); + + comment("Create subscriber"); + + ASSERT_RC(db_subscr_create(dbc, imsi0), 0); + ASSERT_SEL(imsi, imsi0, 0); + + id = g_subscr.id; + ASSERT_SEL_AUD(imsi0, -ENOENT, id); + + comment("Set SQN, but no 3G auth data present"); + + ASSERT_RC(db_update_sqn(dbc, id, 123), -ENOENT); + ASSERT_SEL_AUD(imsi0, -ENOENT, id); + + ASSERT_RC(db_update_sqn(dbc, id, 543), -ENOENT); + ASSERT_SEL_AUD(imsi0, -ENOENT, id); + + comment("Set auth 3G data"); + + ASSERT_RC(db_subscr_update_aud_by_id(dbc, id, + mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, + "BeefedCafeFaceAcedAddedDecadeFee", true, + "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)), + 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + comment("Set SQN"); + + ASSERT_RC(db_update_sqn(dbc, id, 23315), 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_update_sqn(dbc, id, 23315), 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_update_sqn(dbc, id, 423), 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + comment("Set SQN: thru uint64_t range, using the int64_t SQLite bind"); + + ASSERT_RC(db_update_sqn(dbc, id, 0), 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_update_sqn(dbc, id, INT64_MAX), 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_update_sqn(dbc, id, INT64_MIN), 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + ASSERT_RC(db_update_sqn(dbc, id, UINT64_MAX), 0); + ASSERT_SEL_AUD(imsi0, 0, id); + + comment("Delete subscriber"); + + ASSERT_SEL(imsi, imsi0, 0); + ASSERT_RC(db_subscr_delete_by_id(dbc, id), 0); + ASSERT_SEL(imsi, imsi0, -ENOENT); + + comment_end(); +} + +static struct { + bool verbose; +} cmdline_opts = { + .verbose = false, +}; + +static void print_help(const char *program) +{ + printf("Usage:\n" + " %s [-v] [N [N...]]\n" + "Options:\n" + " -h --help show this text.\n" + " -v --verbose print source file and line numbers\n", + program + ); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"verbose", 1, 0, 'v'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hv", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(argv[0]); + exit(0); + case 'v': + cmdline_opts.verbose = true; + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + exit(-1); + break; + } + } + + if (optind < argc) { + fprintf(stderr, "too many args\n"); + exit(-1); + } +} + +int main(int argc, char **argv) +{ + printf("db_test.c\n"); + + ctx = talloc_named_const(NULL, 1, "db_test"); + + handle_options(argc, argv); + + osmo_init_logging(&hlr_log_info); + log_set_print_filename(osmo_stderr_target, cmdline_opts.verbose); + log_set_print_timestamp(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_parse_category_mask(osmo_stderr_target, "DMAIN,1:DDB,1:DAUC,1"); + + /* omit the SQLite version and compilation flags from test output */ + log_set_log_level(osmo_stderr_target, LOGL_ERROR); + dbc = db_open(ctx, "db_test.db"); + log_set_log_level(osmo_stderr_target, 0); + OSMO_ASSERT(dbc); + + test_subscr_create_update_sel_delete(); + test_subscr_aud(); + test_subscr_sqn(); + + printf("Done\n"); + return 0; +} + +/* stubs */ +int auc_compute_vectors(struct osmo_auth_vector *vec, unsigned int num_vec, + struct osmo_sub_auth_data *aud2g, + struct osmo_sub_auth_data *aud3g, + const uint8_t *rand_auts, const uint8_t *auts) +{ OSMO_ASSERT(false); return -1; } +void *lu_op_alloc_conn(void *conn) +{ OSMO_ASSERT(false); return NULL; } +void lu_op_tx_del_subscr_data(void *luop) +{ OSMO_ASSERT(false); } +void lu_op_free(void *luop) +{ OSMO_ASSERT(false); } diff --git a/tests/db/db_test.err b/tests/db/db_test.err new file mode 100644 index 0000000..0b09583 --- /dev/null +++ b/tests/db/db_test.err @@ -0,0 +1,1410 @@ + +===== test_subscr_create_update_sel_delete + +--- Create with valid / invalid IMSI + +db_subscr_create(dbc, imsi0) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', +} + +db_subscr_create(dbc, imsi1) --> 0 + +db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 2, + .imsi = '123456789000001', +} + +db_subscr_create(dbc, imsi2) --> 0 + +db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 3, + .imsi = '123456789000002', +} + +db_subscr_create(dbc, imsi0) --> -EIO +DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi +DAUC IMSI='123456789000000': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', +} + +db_subscr_create(dbc, imsi1) --> -EIO +DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi +DAUC IMSI='123456789000001': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi + +db_subscr_create(dbc, imsi1) --> -EIO +DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi +DAUC IMSI='123456789000001': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi + +db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 2, + .imsi = '123456789000001', +} + +db_subscr_create(dbc, imsi2) --> -EIO +DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi +DAUC IMSI='123456789000002': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi + +db_subscr_create(dbc, imsi2) --> -EIO +DDB (2067) abort at 18 in [INSERT INTO subscriber (imsi) VALUES ($imsi)]: UNIQUE constraint failed: subscriber.imsi +DAUC IMSI='123456789000002': Cannot create subscriber: SQL error: (2067) UNIQUE constraint failed: subscriber.imsi + +db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 3, + .imsi = '123456789000002', +} + +db_subscr_create(dbc, "123456789 000003") --> -EINVAL +DAUC Cannot create subscriber: invalid IMSI: '123456789 000003' + +db_subscr_get_by_imsi(dbc, "123456789000003", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123456789000003': No such subscriber + +db_subscr_create(dbc, "123456789000002123456") --> -EINVAL +DAUC Cannot create subscriber: invalid IMSI: '123456789000002123456' + +db_subscr_get_by_imsi(dbc, "123456789000002123456", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123456789000002123456': No such subscriber + +db_subscr_create(dbc, "foobar123") --> -EINVAL +DAUC Cannot create subscriber: invalid IMSI: 'foobar123' + +db_subscr_get_by_imsi(dbc, "foobar123", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='foobar123': No such subscriber + +db_subscr_create(dbc, "123") --> -EINVAL +DAUC Cannot create subscriber: invalid IMSI: '123' + +db_subscr_get_by_imsi(dbc, "123", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123': No such subscriber + +db_subscr_create(dbc, short_imsi) --> 0 + +db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 4, + .imsi = '123456', +} + + +--- Set valid / invalid MSISDN + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', +} + +db_subscr_update_msisdn_by_imsi(dbc, imsi0, "54321") --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '54321', +} + +db_subscr_get_by_msisdn(dbc, "54321", &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '54321', +} + +db_subscr_update_msisdn_by_imsi(dbc, imsi0, "54321012345678912345678") --> -EINVAL +DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: '54321012345678912345678' + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '54321', +} + +db_subscr_get_by_msisdn(dbc, "54321", &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '54321', +} + +db_subscr_get_by_msisdn(dbc, "54321012345678912345678", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: MSISDN='54321012345678912345678': No such subscriber + +db_subscr_update_msisdn_by_imsi(dbc, imsi0, "543 21") --> -EINVAL +DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: '543 21' + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '54321', +} + +db_subscr_get_by_msisdn(dbc, "543 21", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: MSISDN='543 21': No such subscriber + +db_subscr_update_msisdn_by_imsi(dbc, imsi0, "foobar123") --> -EINVAL +DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: 'foobar123' + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '54321', +} + +db_subscr_get_by_msisdn(dbc, "foobar123", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: MSISDN='foobar123': No such subscriber + +db_subscr_update_msisdn_by_imsi(dbc, imsi0, "5") --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '5', +} + +db_subscr_get_by_msisdn(dbc, "5", &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '5', +} + +db_subscr_get_by_msisdn(dbc, "54321", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: MSISDN='54321': No such subscriber + +db_subscr_update_msisdn_by_imsi(dbc, imsi0, "543210123456789") --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + +db_subscr_get_by_msisdn(dbc, "543210123456789", &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + +db_subscr_update_msisdn_by_imsi(dbc, imsi0, "5432101234567891") --> -EINVAL +DAUC IMSI='123456789000000': Cannot update subscriber: invalid MSISDN: '5432101234567891' + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + +db_subscr_get_by_msisdn(dbc, "5432101234567891", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: MSISDN='5432101234567891': No such subscriber + + +--- Set MSISDN on non-existent / invalid IMSI + +db_subscr_update_msisdn_by_imsi(dbc, unknown_imsi, "99") --> -ENOENT +DAUC Cannot update MSISDN: no such subscriber: IMSI='999999999' + +db_subscr_get_by_msisdn(dbc, "99", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: MSISDN='99': No such subscriber + +db_subscr_update_msisdn_by_imsi(dbc, "foobar", "99") --> -ENOENT +DAUC Cannot update MSISDN: no such subscriber: IMSI='foobar' + +db_subscr_get_by_msisdn(dbc, "99", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: MSISDN='99': No such subscriber + + +--- Set / unset nam_cs and nam_ps + +db_subscr_nam(dbc, imsi0, false, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .nam_ps = false, +} + +db_subscr_nam(dbc, imsi0, false, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .nam_cs = false, + .nam_ps = false, +} + +db_subscr_nam(dbc, imsi0, true, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .nam_ps = false, +} + +db_subscr_nam(dbc, imsi0, true, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + + +--- Set / unset nam_cs and nam_ps *again* + +db_subscr_nam(dbc, imsi0, false, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .nam_ps = false, +} + +db_subscr_nam(dbc, imsi0, false, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .nam_ps = false, +} + +db_subscr_nam(dbc, imsi0, false, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .nam_cs = false, + .nam_ps = false, +} + +db_subscr_nam(dbc, imsi0, false, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .nam_cs = false, + .nam_ps = false, +} + +db_subscr_nam(dbc, imsi0, true, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .nam_cs = false, +} + +db_subscr_nam(dbc, imsi0, true, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .nam_cs = false, +} + +db_subscr_nam(dbc, imsi0, true, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + +db_subscr_nam(dbc, imsi0, true, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + + +--- Set nam_cs and nam_ps on non-existent / invalid IMSI + +db_subscr_nam(dbc, unknown_imsi, false, true) --> -ENOENT +DAUC Cannot disable PS: no such subscriber: IMSI='999999999' + +db_subscr_nam(dbc, unknown_imsi, false, false) --> -ENOENT +DAUC Cannot disable CS: no such subscriber: IMSI='999999999' + +db_subscr_get_by_imsi(dbc, unknown_imsi, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='999999999': No such subscriber + +db_subscr_nam(dbc, "foobar", false, true) --> -ENOENT +DAUC Cannot disable PS: no such subscriber: IMSI='foobar' + +db_subscr_nam(dbc, "foobar", false, false) --> -ENOENT +DAUC Cannot disable CS: no such subscriber: IMSI='foobar' + + +--- Record LU for PS and CS (SGSN and VLR names) + +db_subscr_lu(dbc, id0, "5952", true) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .sgsn_number = '5952', +} + +db_subscr_lu(dbc, id0, "712", false) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .vlr_number = '712', + .sgsn_number = '5952', +} + + +--- Record LU for PS and CS (SGSN and VLR names) *again* + +db_subscr_lu(dbc, id0, "111", true) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .vlr_number = '712', + .sgsn_number = '111', +} + +db_subscr_lu(dbc, id0, "111", true) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .vlr_number = '712', + .sgsn_number = '111', +} + +db_subscr_lu(dbc, id0, "222", false) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .vlr_number = '222', + .sgsn_number = '111', +} + +db_subscr_lu(dbc, id0, "222", false) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .vlr_number = '222', + .sgsn_number = '111', +} + + +--- Unset LU info for PS and CS (SGSN and VLR names) + +db_subscr_lu(dbc, id0, "", true) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .vlr_number = '222', +} + +db_subscr_lu(dbc, id0, "", false) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + +db_subscr_lu(dbc, id0, "111", true) --> 0 + +db_subscr_lu(dbc, id0, "222", false) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .vlr_number = '222', + .sgsn_number = '111', +} + +db_subscr_lu(dbc, id0, NULL, true) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .vlr_number = '222', +} + +db_subscr_lu(dbc, id0, NULL, false) --> 0 + +db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + + +--- Record LU for non-existent ID + +db_subscr_lu(dbc, 99999, "5952", true) --> -ENOENT +DAUC Cannot update SGSN number for subscriber ID=99999: no such subscriber + +db_subscr_lu(dbc, 99999, "712", false) --> -ENOENT +DAUC Cannot update VLR number for subscriber ID=99999: no such subscriber + +db_subscr_get_by_id(dbc, 99999, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: ID=99999: No such subscriber + + +--- Purge and un-purge PS and CS + +db_subscr_purge(dbc, imsi0, true, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .ms_purged_ps = true, +} + +db_subscr_purge(dbc, imsi0, true, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .ms_purged_cs = true, + .ms_purged_ps = true, +} + +db_subscr_purge(dbc, imsi0, false, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .ms_purged_ps = true, +} + +db_subscr_purge(dbc, imsi0, false, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + + +--- Purge PS and CS *again* + +db_subscr_purge(dbc, imsi0, true, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .ms_purged_ps = true, +} + +db_subscr_purge(dbc, imsi0, true, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .ms_purged_ps = true, +} + +db_subscr_purge(dbc, imsi0, false, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + +db_subscr_purge(dbc, imsi0, false, true) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + +db_subscr_purge(dbc, imsi0, true, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .ms_purged_cs = true, +} + +db_subscr_purge(dbc, imsi0, true, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .ms_purged_cs = true, +} + +db_subscr_purge(dbc, imsi0, false, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + +db_subscr_purge(dbc, imsi0, false, false) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + + +--- Purge on non-existent / invalid IMSI + +db_subscr_purge(dbc, unknown_imsi, true, true) --> -ENOENT +DAUC Cannot purge PS: no such subscriber: IMSI='999999999' + +db_subscr_get_by_imsi(dbc, unknown_imsi, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='999999999': No such subscriber + +db_subscr_purge(dbc, unknown_imsi, true, false) --> -ENOENT +DAUC Cannot purge CS: no such subscriber: IMSI='999999999' + +db_subscr_get_by_imsi(dbc, unknown_imsi, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='999999999': No such subscriber + + +--- Delete non-existent / invalid IDs + +db_subscr_delete_by_id(dbc, 999) --> -ENOENT +DAUC Cannot delete: no such subscriber: ID=999 + +db_subscr_delete_by_id(dbc, -10) --> -ENOENT +DAUC Cannot delete: no such subscriber: ID=-10 + + +--- Delete subscribers + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', +} + +db_subscr_delete_by_id(dbc, id0) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber + +db_subscr_delete_by_id(dbc, id0) --> -ENOENT +DAUC Cannot delete: no such subscriber: ID=1 + +db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 2, + .imsi = '123456789000001', +} + +db_subscr_delete_by_id(dbc, id1) --> 0 + +db_subscr_get_by_imsi(dbc, imsi1, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123456789000001': No such subscriber + +db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 3, + .imsi = '123456789000002', +} + +db_subscr_delete_by_id(dbc, id2) --> 0 + +db_subscr_get_by_imsi(dbc, imsi2, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123456789000002': No such subscriber + +db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 4, + .imsi = '123456', +} + +db_subscr_delete_by_id(dbc, id_short) --> 0 + +db_subscr_get_by_imsi(dbc, short_imsi, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123456': No such subscriber + +===== test_subscr_create_update_sel_delete: SUCCESS + + +===== test_subscr_aud + +--- Get auth data for non-existent subscriber + +db_get_auth_data(dbc, unknown_imsi, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='999999999': No such subscriber + + + +--- Create subscriber + +db_subscr_create(dbc, imsi0) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', +} + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='123456789000000': No 2G Auth Data +DAUC IMSI='123456789000000': No 3G Auth Data + + + +--- Set auth data, 2G only + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 3G Auth Data + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v1, + .u.gsm.ki = '0123456789abcdef0123456789abcdef', +} +3G: none + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_COMP128v1, "0123456789abcdef0123456789abcdef")) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 3G Auth Data + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v1, + .u.gsm.ki = '0123456789abcdef0123456789abcdef', +} +3G: none + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_COMP128v2, "BeadedBeeAced1EbbedDefacedFacade")) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 3G Auth Data + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v2, + .u.gsm.ki = 'beadedbeeaced1ebbeddefacedfacade', +} +3G: none + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_COMP128v3, "DeafBeddedBabeAcceededFadedDecaf")) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 3G Auth Data + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'deafbeddedbabeacceededfadeddecaf', +} +3G: none + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_XOR, "CededEffacedAceFacedBadFadedBeef")) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 3G Auth Data + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = XOR, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: none + + +--- Remove 2G auth data + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='123456789000000': No 2G Auth Data +DAUC IMSI='123456789000000': No 3G Auth Data + + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)) --> -ENOENT + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_XOR, "CededEffacedAceFacedBadFadedBeef")) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 3G Auth Data + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = XOR, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: none + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_NONE, "f000000000000f00000000000f000000")) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='123456789000000': No 2G Auth Data +DAUC IMSI='123456789000000': No 3G Auth Data + + + +--- Set auth data, 3G only + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "BeefedCafeFaceAcedAddedDecadeFee", true, "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "BeefedCafeFaceAcedAddedDecadeFee", true, "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "Deaf0ff1ceD0d0DabbedD1ced1ceF00d", true, "F1bbed0afD0eF0bD0ffed0ddF1fe0b0e", 0)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'deaf0ff1ced0d0dabbedd1ced1cef00d', + .u.umts.opc_is_op = 1, + .u.umts.k = 'f1bbed0afd0ef0bd0ffed0ddf1fe0b0e', + .u.umts.amf = '0000', +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "BeefedCafeFaceAcedAddedDecadeFee", false, "DeafBeddedBabeAcceededFadedDecaf", OSMO_MILENAGE_IND_BITLEN_MAX)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 28, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "CededEffacedAceFacedBadFadedBeef", false, "BeefedCafeFaceAcedAddedDecadeFee", 5)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'cededeffacedacefacedbadfadedbeef', + .u.umts.opc_is_op = 0, + .u.umts.k = 'beefedcafefaceacedaddeddecadefee', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + + +--- Remove 3G auth data + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='123456789000000': No 2G Auth Data +DAUC IMSI='123456789000000': No 3G Auth Data + + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)) --> -ENOENT + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "CededEffacedAceFacedBadFadedBeef", false, "BeefedCafeFaceAcedAddedDecadeFee", 5)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'cededeffacedacefacedbadfadedbeef', + .u.umts.opc_is_op = 0, + .u.umts.k = 'beefedcafefaceacedaddeddecadefee', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_NONE, "asdfasdfasd", false, "asdfasdfasdf", 99999)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='123456789000000': No 2G Auth Data +DAUC IMSI='123456789000000': No 3G Auth Data + + + +--- Set auth data, 2G and 3G + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_COMP128v3, "CededEffacedAceFacedBadFadedBeef")) --> 0 + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "BeefedCafeFaceAcedAddedDecadeFee", false, "DeafBeddedBabeAcceededFadedDecaf", 5)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + + +--- Set invalid auth data + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(99999, "f000000000000f00000000000f000000")) --> -EINVAL +DAUC Cannot update auth tokens: Unknown auth algo: 99999 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_XOR, "f000000000000f00000000000f000000f00000000")) --> -EINVAL +DAUC Cannot update auth tokens: Invalid KI: 'f000000000000f00000000000f000000f00000000' + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_XOR, "f00")) --> -EINVAL +DAUC Cannot update auth tokens: Invalid KI: 'f00' + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_MILENAGE, "0123456789abcdef0123456789abcdef")) --> -EINVAL +DAUC Cannot update auth tokens: auth algo not suited for 2G: MILENAGE + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "0f000000000000f00000000000f000000", false, "f000000000000f00000000000f000000", 5)) --> -EINVAL +DAUC Cannot update auth tokens: Invalid OP/OPC: '0f000000000000f00000000000f000000' + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "f000000000000f00000000000f000000", false, "000000000000f00000000000f000000", 5)) --> -EINVAL +DAUC Cannot update auth tokens: Invalid K: '000000000000f00000000000f000000' + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "f000000000000f00000000000f000000", false, "f000000000000f00000000000f000000", OSMO_MILENAGE_IND_BITLEN_MAX + 1)) --> -EINVAL +DAUC Cannot update auth tokens: Invalid ind_bitlen: 29 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "X000000000000f00000000000f000000", false, "f000000000000f00000000000f000000", 5)) --> -EINVAL +DAUC Cannot update auth tokens: Invalid OP/OPC: 'X000000000000f00000000000f000000' + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "f000000000000f00000000000f000000", false, "f000000000000 f00000000000 f000000", 5)) --> -EINVAL +DAUC Cannot update auth tokens: Invalid K: 'f000000000000 f00000000000 f000000' + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 + +2G: struct osmo_sub_auth_data { + .type = GSM, + .algo = COMP128v3, + .u.gsm.ki = 'cededeffacedacefacedbadfadedbeef', +} +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 0, + .u.umts.k = 'deafbeddedbabeacceededfadeddecaf', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + + +--- Delete subscriber + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', +} + +db_subscr_delete_by_id(dbc, id) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber + + +--- Re-add subscriber and verify auth data didn't come back + +db_subscr_create(dbc, imsi0) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', +} + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='123456789000000': No 2G Auth Data +DAUC IMSI='123456789000000': No 3G Auth Data + + +db_subscr_delete_by_id(dbc, id) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber + +===== test_subscr_aud: SUCCESS + + +===== test_subscr_sqn + +--- Set SQN for unknown subscriber + +db_update_sqn(dbc, 99, 999) --> -ENOENT +DAUC Cannot update SQN for subscriber ID=99: no auc_3g entry for such subscriber + +db_subscr_get_by_id(dbc, 99, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: ID=99: No such subscriber + +db_update_sqn(dbc, 9999, 99) --> -ENOENT +DAUC Cannot update SQN for subscriber ID=9999: no auc_3g entry for such subscriber + +db_subscr_get_by_id(dbc, 9999, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: ID=9999: No such subscriber + + +--- Create subscriber + +db_subscr_create(dbc, imsi0) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', +} + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='123456789000000': No 2G Auth Data +DAUC IMSI='123456789000000': No 3G Auth Data + + + +--- Set SQN, but no 3G auth data present + +db_update_sqn(dbc, id, 123) --> -ENOENT +DAUC Cannot update SQN for subscriber ID=1: no auc_3g entry for such subscriber + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='123456789000000': No 2G Auth Data +DAUC IMSI='123456789000000': No 3G Auth Data + + +db_update_sqn(dbc, id, 543) --> -ENOENT +DAUC Cannot update SQN for subscriber ID=1: no auc_3g entry for such subscriber + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -2 +DAUC IMSI='123456789000000': No 2G Auth Data +DAUC IMSI='123456789000000': No 3G Auth Data + + + +--- Set auth 3G data + +db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_MILENAGE, "BeefedCafeFaceAcedAddedDecadeFee", true, "C01ffedC1cadaeAc1d1f1edAcac1aB0a", 5)) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + + +--- Set SQN + +db_update_sqn(dbc, id, 23315) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.sqn = 23315, + .u.umts.sqn = 0x5b13, + .u.umts.ind_bitlen = 5, +} + +db_update_sqn(dbc, id, 23315) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.sqn = 23315, + .u.umts.sqn = 0x5b13, + .u.umts.ind_bitlen = 5, +} + +db_update_sqn(dbc, id, 423) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.sqn = 423, + .u.umts.sqn = 0x1a7, + .u.umts.ind_bitlen = 5, +} + + +--- Set SQN: thru uint64_t range, using the int64_t SQLite bind + +db_update_sqn(dbc, id, 0) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.ind_bitlen = 5, +} + +db_update_sqn(dbc, id, INT64_MAX) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.sqn = 9223372036854775807, + .u.umts.sqn = 0x7fffffffffffffff, + .u.umts.ind_bitlen = 5, +} + +db_update_sqn(dbc, id, INT64_MIN) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.sqn = 9223372036854775808, + .u.umts.sqn = 0x8000000000000000, + .u.umts.ind_bitlen = 5, +} + +db_update_sqn(dbc, id, UINT64_MAX) --> 0 + +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> 0 +DAUC IMSI='123456789000000': No 2G Auth Data + +2G: none +3G: struct osmo_sub_auth_data { + .type = UMTS, + .algo = MILENAGE, + .u.umts.opc = 'beefedcafefaceacedaddeddecadefee', + .u.umts.opc_is_op = 1, + .u.umts.k = 'c01ffedc1cadaeac1d1f1edacac1ab0a', + .u.umts.amf = '0000', + .u.umts.sqn = 18446744073709551615, + .u.umts.sqn = 0xffffffffffffffff, + .u.umts.ind_bitlen = 5, +} + + +--- Delete subscriber + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', +} + +db_subscr_delete_by_id(dbc, id) --> 0 + +db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMSI='123456789000000': No such subscriber + +===== test_subscr_sqn: SUCCESS + diff --git a/tests/db/db_test.ok b/tests/db/db_test.ok new file mode 100644 index 0000000..26cefd1 --- /dev/null +++ b/tests/db/db_test.ok @@ -0,0 +1,2 @@ +db_test.c +Done diff --git a/tests/gsup_server/Makefile.am b/tests/gsup_server/Makefile.am new file mode 100644 index 0000000..fee60f5 --- /dev/null +++ b/tests/gsup_server/Makefile.am @@ -0,0 +1,40 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/src \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(NULL) + +EXTRA_DIST = \ + gsup_server_test.ok \ + gsup_server_test.err \ + $(NULL) + +noinst_PROGRAMS = \ + gsup_server_test \ + $(NULL) + +gsup_server_test_SOURCES = \ + gsup_server_test.c \ + $(NULL) + +gsup_server_test_LDADD = \ + $(top_srcdir)/src/gsup_server.c \ + $(top_srcdir)/src/gsup_router.c \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(NULL) + +.PHONY: update_exp +update_exp: + $(builddir)/gsup_server_test >"$(srcdir)/gsup_server_test.ok" 2>"$(srcdir)/gsup_server_test.err" diff --git a/tests/gsup_server/gsup_server_test.c b/tests/gsup_server/gsup_server_test.c new file mode 100644 index 0000000..cc475be --- /dev/null +++ b/tests/gsup_server/gsup_server_test.c @@ -0,0 +1,145 @@ +/* (C) 2017 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include "gsup_server.h" + +#define comment_start() printf("\n===== %s\n", __func__) +#define comment_end() printf("===== %s: SUCCESS\n\n", __func__) +#define btw(fmt, args...) printf("\n" fmt "\n", ## args) + +#define VERBOSE_ASSERT(val, expect_op, fmt) \ + do { \ + printf(#val " == " fmt "\n", (val)); \ + OSMO_ASSERT((val) expect_op); \ + } while (0) + +void osmo_gsup_server_add_conn(struct llist_head *clients, + struct osmo_gsup_conn *conn); + +static void test_add_conn(void) +{ + struct llist_head _list; + struct llist_head *clients = &_list; + struct osmo_gsup_conn conn_inst[23] = {}; + struct osmo_gsup_conn *conn; + unsigned int i; + + comment_start(); + + INIT_LLIST_HEAD(clients); + + btw("Add 10 items"); + for (i = 0; i < 10; i++) { + osmo_gsup_server_add_conn(clients, &conn_inst[i]); + printf("conn_inst[%u].auc_3g_ind == %u\n", i, conn_inst[i].auc_3g_ind); + OSMO_ASSERT(clients->next == &conn_inst[0].list); + } + + btw("Expecting a list of 0..9"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + OSMO_ASSERT(conn->auc_3g_ind == i); + OSMO_ASSERT(conn == &conn_inst[i]); + i++; + } + + btw("Punch two holes in the sequence in arbitrary order," + " a larger one from 2..4 and a single one at 7."); + llist_del(&conn_inst[4].list); + llist_del(&conn_inst[2].list); + llist_del(&conn_inst[3].list); + llist_del(&conn_inst[7].list); + + btw("Expecting a list of 0,1, 5,6, 8,9"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + i++; + } + + btw("Add conns, expecting them to take the open slots"); + osmo_gsup_server_add_conn(clients, &conn_inst[12]); + VERBOSE_ASSERT(conn_inst[12].auc_3g_ind, == 2, "%u"); + + osmo_gsup_server_add_conn(clients, &conn_inst[13]); + VERBOSE_ASSERT(conn_inst[13].auc_3g_ind, == 3, "%u"); + + osmo_gsup_server_add_conn(clients, &conn_inst[14]); + VERBOSE_ASSERT(conn_inst[14].auc_3g_ind, == 4, "%u"); + + osmo_gsup_server_add_conn(clients, &conn_inst[17]); + VERBOSE_ASSERT(conn_inst[17].auc_3g_ind, == 7, "%u"); + + osmo_gsup_server_add_conn(clients, &conn_inst[18]); + VERBOSE_ASSERT(conn_inst[18].auc_3g_ind, == 10, "%u"); + + btw("Expecting a list of 0..10"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + OSMO_ASSERT(conn->auc_3g_ind == i); + i++; + } + + btw("Does it also work for the first item?"); + llist_del(&conn_inst[0].list); + + btw("Expecting a list of 1..10"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + OSMO_ASSERT(conn->auc_3g_ind == i + 1); + i++; + } + + btw("Add another conn, should take auc_3g_ind == 0"); + osmo_gsup_server_add_conn(clients, &conn_inst[20]); + VERBOSE_ASSERT(conn_inst[20].auc_3g_ind, == 0, "%u"); + + btw("Expecting a list of 0..10"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + OSMO_ASSERT(conn->auc_3g_ind == i); + i++; + } + + btw("If a client reconnects, it will (likely) get the same auc_3g_ind"); + VERBOSE_ASSERT(conn_inst[5].auc_3g_ind, == 5, "%u"); + llist_del(&conn_inst[5].list); + conn_inst[5].auc_3g_ind = 423; + osmo_gsup_server_add_conn(clients, &conn_inst[5]); + VERBOSE_ASSERT(conn_inst[5].auc_3g_ind, == 5, "%u"); + + comment_end(); +} + +int main(int argc, char **argv) +{ + printf("test_gsup_server.c\n"); + + test_add_conn(); + + printf("Done\n"); + return 0; +} diff --git a/tests/gsup_server/gsup_server_test.err b/tests/gsup_server/gsup_server_test.err new file mode 100644 index 0000000..e69de29 diff --git a/tests/gsup_server/gsup_server_test.ok b/tests/gsup_server/gsup_server_test.ok new file mode 100644 index 0000000..80d944c --- /dev/null +++ b/tests/gsup_server/gsup_server_test.ok @@ -0,0 +1,94 @@ +test_gsup_server.c + +===== test_add_conn + +Add 10 items +conn_inst[0].auc_3g_ind == 0 +conn_inst[1].auc_3g_ind == 1 +conn_inst[2].auc_3g_ind == 2 +conn_inst[3].auc_3g_ind == 3 +conn_inst[4].auc_3g_ind == 4 +conn_inst[5].auc_3g_ind == 5 +conn_inst[6].auc_3g_ind == 6 +conn_inst[7].auc_3g_ind == 7 +conn_inst[8].auc_3g_ind == 8 +conn_inst[9].auc_3g_ind == 9 + +Expecting a list of 0..9 +conn[0].auc_3g_ind == 0 +conn[1].auc_3g_ind == 1 +conn[2].auc_3g_ind == 2 +conn[3].auc_3g_ind == 3 +conn[4].auc_3g_ind == 4 +conn[5].auc_3g_ind == 5 +conn[6].auc_3g_ind == 6 +conn[7].auc_3g_ind == 7 +conn[8].auc_3g_ind == 8 +conn[9].auc_3g_ind == 9 + +Punch two holes in the sequence in arbitrary order, a larger one from 2..4 and a single one at 7. + +Expecting a list of 0,1, 5,6, 8,9 +conn[0].auc_3g_ind == 0 +conn[1].auc_3g_ind == 1 +conn[2].auc_3g_ind == 5 +conn[3].auc_3g_ind == 6 +conn[4].auc_3g_ind == 8 +conn[5].auc_3g_ind == 9 + +Add conns, expecting them to take the open slots +conn_inst[12].auc_3g_ind == 2 +conn_inst[13].auc_3g_ind == 3 +conn_inst[14].auc_3g_ind == 4 +conn_inst[17].auc_3g_ind == 7 +conn_inst[18].auc_3g_ind == 10 + +Expecting a list of 0..10 +conn[0].auc_3g_ind == 0 +conn[1].auc_3g_ind == 1 +conn[2].auc_3g_ind == 2 +conn[3].auc_3g_ind == 3 +conn[4].auc_3g_ind == 4 +conn[5].auc_3g_ind == 5 +conn[6].auc_3g_ind == 6 +conn[7].auc_3g_ind == 7 +conn[8].auc_3g_ind == 8 +conn[9].auc_3g_ind == 9 +conn[10].auc_3g_ind == 10 + +Does it also work for the first item? + +Expecting a list of 1..10 +conn[0].auc_3g_ind == 1 +conn[1].auc_3g_ind == 2 +conn[2].auc_3g_ind == 3 +conn[3].auc_3g_ind == 4 +conn[4].auc_3g_ind == 5 +conn[5].auc_3g_ind == 6 +conn[6].auc_3g_ind == 7 +conn[7].auc_3g_ind == 8 +conn[8].auc_3g_ind == 9 +conn[9].auc_3g_ind == 10 + +Add another conn, should take auc_3g_ind == 0 +conn_inst[20].auc_3g_ind == 0 + +Expecting a list of 0..10 +conn[0].auc_3g_ind == 0 +conn[1].auc_3g_ind == 1 +conn[2].auc_3g_ind == 2 +conn[3].auc_3g_ind == 3 +conn[4].auc_3g_ind == 4 +conn[5].auc_3g_ind == 5 +conn[6].auc_3g_ind == 6 +conn[7].auc_3g_ind == 7 +conn[8].auc_3g_ind == 8 +conn[9].auc_3g_ind == 9 +conn[10].auc_3g_ind == 10 + +If a client reconnects, it will (likely) get the same auc_3g_ind +conn_inst[5].auc_3g_ind == 5 +conn_inst[5].auc_3g_ind == 5 +===== test_add_conn: SUCCESS + +Done diff --git a/tests/test_nodes.vty b/tests/test_nodes.vty new file mode 100644 index 0000000..6de673a --- /dev/null +++ b/tests/test_nodes.vty @@ -0,0 +1,117 @@ +OsmoHLR> list + show version + show online-help + list + exit + help + enable + terminal length <0-512> + terminal no length + who + show history + logging enable +... + show logging vty + show alarms + subscriber (imsi|msisdn|id) IDENT show + +OsmoHLR> enable +OsmoHLR# list + help + list + write terminal + write file + write memory + write + show running-config + exit + disable + configure terminal + copy running-config startup-config + show startup-config + show version + show online-help + terminal length <0-512> + terminal no length + who + show history + terminal monitor + terminal no monitor + logging enable +... + +OsmoHLR# configure terminal +OsmoHLR(config)# list + help + list + write terminal + write file + write memory + write + show running-config + exit + end +... + hlr + +OsmoHLR(config)# hlr +OsmoHLR(config-hlr)# list + help + list + write terminal + write file + write memory + write + show running-config + exit + end + gsup + +OsmoHLR(config-hlr)# gsup +OsmoHLR(config-hlr-gsup)# list + help + list + write terminal + write file + write memory + write + show running-config + exit + end + bind ip A.B.C.D + +OsmoHLR(config-hlr-gsup)# exit +OsmoHLR(config-hlr)# exit +OsmoHLR(config)# exit +OsmoHLR# configure terminal +OsmoHLR(config)# hlr +OsmoHLR(config-hlr)# gsup +OsmoHLR(config-hlr-gsup)# end +OsmoHLR# disable +OsmoHLR> enable + +OsmoHLR# show running-config + +Current configuration: +! +! +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging print extended-timestamp 1 + logging level all debug + logging level main notice + logging level db notice + logging level auc notice +... +! +line vty + no login +! +ctrl + bind 127.0.0.1 +hlr + gsup + bind ip 127.0.0.1 +end diff --git a/tests/test_subscriber.ctrl b/tests/test_subscriber.ctrl new file mode 100644 index 0000000..4cefa4d --- /dev/null +++ b/tests/test_subscriber.ctrl @@ -0,0 +1,614 @@ +GET 1 subscriber.by-imsi-901990000000001.info +GET_REPLY 1 subscriber.by-imsi-901990000000001.info +id 1 +imsi 901990000000001 +msisdn 1 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +GET 2 subscriber.by-imsi-901990000000001.info-aud +GET_REPLY 2 subscriber.by-imsi-901990000000001.info-aud +aud2g.algo COMP128v1 +aud2g.ki 000102030405060708090a0b0c0d0e0f + +GET 3 subscriber.by-imsi-901990000000001.info-all +GET_REPLY 3 subscriber.by-imsi-901990000000001.info-all +id 1 +imsi 901990000000001 +msisdn 1 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 +aud2g.algo COMP128v1 +aud2g.ki 000102030405060708090a0b0c0d0e0f + +GET 4 subscriber.by-imsi-901990000000002.info +GET_REPLY 4 subscriber.by-imsi-901990000000002.info +id 2 +imsi 901990000000002 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +GET 5 subscriber.by-imsi-901990000000002.info-aud +GET_REPLY 5 subscriber.by-imsi-901990000000002.info-aud +aud3g.algo MILENAGE +aud3g.k 000102030405060708090a0b0c0d0e0f +aud3g.opc 101112131415161718191a1b1c1d1e1f +aud3g.ind_bitlen 5 +aud3g.sqn 4223 + +GET 6 subscriber.by-imsi-901990000000002.info-all +GET_REPLY 6 subscriber.by-imsi-901990000000002.info-all +id 2 +imsi 901990000000002 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 +aud3g.algo MILENAGE +aud3g.k 000102030405060708090a0b0c0d0e0f +aud3g.opc 101112131415161718191a1b1c1d1e1f +aud3g.ind_bitlen 5 +aud3g.sqn 4223 + +GET 7 subscriber.by-imsi-901990000000003.info +GET_REPLY 7 subscriber.by-imsi-901990000000003.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +GET 8 subscriber.by-imsi-901990000000003.info-aud +GET_REPLY 8 subscriber.by-imsi-901990000000003.info-aud +aud2g.algo COMP128v1 +aud2g.ki 000102030405060708090a0b0c0d0e0f +aud3g.algo MILENAGE +aud3g.k 000102030405060708090a0b0c0d0e0f +aud3g.opc 101112131415161718191a1b1c1d1e1f +aud3g.ind_bitlen 5 +aud3g.sqn 2342 + +GET 9 subscriber.by-imsi-901990000000003.info-all +GET_REPLY 9 subscriber.by-imsi-901990000000003.info-all +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 +aud2g.algo COMP128v1 +aud2g.ki 000102030405060708090a0b0c0d0e0f +aud3g.algo MILENAGE +aud3g.k 000102030405060708090a0b0c0d0e0f +aud3g.opc 101112131415161718191a1b1c1d1e1f +aud3g.ind_bitlen 5 +aud3g.sqn 2342 + +GET 10 subscriber.by-imsi-901990000000003.ps-enabled +GET_REPLY 10 subscriber.by-imsi-901990000000003.ps-enabled 1 + +SET 11 subscriber.by-imsi-901990000000003.ps-enabled 0 +SET_REPLY 11 subscriber.by-imsi-901990000000003.ps-enabled OK +GET 12 subscriber.by-imsi-901990000000003.ps-enabled +GET_REPLY 12 subscriber.by-imsi-901990000000003.ps-enabled 0 + +GET 13 subscriber.by-imsi-901990000000003.info +GET_REPLY 13 subscriber.by-imsi-901990000000003.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 0 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 14 subscriber.by-imsi-901990000000003.ps-enabled 0 +SET_REPLY 14 subscriber.by-imsi-901990000000003.ps-enabled OK +GET 15 subscriber.by-imsi-901990000000003.ps-enabled +GET_REPLY 15 subscriber.by-imsi-901990000000003.ps-enabled 0 + +SET 16 subscriber.by-imsi-901990000000003.ps-enabled 1 +SET_REPLY 16 subscriber.by-imsi-901990000000003.ps-enabled OK +GET 17 subscriber.by-imsi-901990000000003.ps-enabled +GET_REPLY 17 subscriber.by-imsi-901990000000003.ps-enabled 1 + +GET 18 subscriber.by-imsi-901990000000003.info +GET_REPLY 18 subscriber.by-imsi-901990000000003.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 19 subscriber.by-imsi-901990000000003.ps-enabled 1 +SET_REPLY 19 subscriber.by-imsi-901990000000003.ps-enabled OK +GET 20 subscriber.by-imsi-901990000000003.ps-enabled +GET_REPLY 20 subscriber.by-imsi-901990000000003.ps-enabled 1 + +GET 21 subscriber.by-imsi-901990000000003.cs-enabled +GET_REPLY 21 subscriber.by-imsi-901990000000003.cs-enabled 1 + +SET 22 subscriber.by-imsi-901990000000003.cs-enabled 0 +SET_REPLY 22 subscriber.by-imsi-901990000000003.cs-enabled OK +GET 23 subscriber.by-imsi-901990000000003.cs-enabled +GET_REPLY 23 subscriber.by-imsi-901990000000003.cs-enabled 0 + +GET 24 subscriber.by-imsi-901990000000003.info +GET_REPLY 24 subscriber.by-imsi-901990000000003.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 0 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 25 subscriber.by-imsi-901990000000003.cs-enabled 0 +SET_REPLY 25 subscriber.by-imsi-901990000000003.cs-enabled OK +GET 26 subscriber.by-imsi-901990000000003.cs-enabled +GET_REPLY 26 subscriber.by-imsi-901990000000003.cs-enabled 0 + +SET 27 subscriber.by-imsi-901990000000003.cs-enabled 1 +SET_REPLY 27 subscriber.by-imsi-901990000000003.cs-enabled OK +GET 28 subscriber.by-imsi-901990000000003.cs-enabled +GET_REPLY 28 subscriber.by-imsi-901990000000003.cs-enabled 1 + +GET 29 subscriber.by-imsi-901990000000003.info +GET_REPLY 29 subscriber.by-imsi-901990000000003.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 30 subscriber.by-imsi-901990000000003.cs-enabled 1 +SET_REPLY 30 subscriber.by-imsi-901990000000003.cs-enabled OK +GET 31 subscriber.by-imsi-901990000000003.cs-enabled +GET_REPLY 31 subscriber.by-imsi-901990000000003.cs-enabled 1 + +SET 32 subscriber.by-imsi-901990000000003.ps-enabled 0 +SET_REPLY 32 subscriber.by-imsi-901990000000003.ps-enabled OK +SET 33 subscriber.by-imsi-901990000000003.cs-enabled 0 +SET_REPLY 33 subscriber.by-imsi-901990000000003.cs-enabled OK +GET 34 subscriber.by-imsi-901990000000003.info +GET_REPLY 34 subscriber.by-imsi-901990000000003.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 0 +nam_ps 0 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 35 subscriber.by-imsi-901990000000003.ps-enabled 1 +SET_REPLY 35 subscriber.by-imsi-901990000000003.ps-enabled OK +SET 36 subscriber.by-imsi-901990000000003.cs-enabled 1 +SET_REPLY 36 subscriber.by-imsi-901990000000003.cs-enabled OK +GET 37 subscriber.by-imsi-901990000000003.info +GET_REPLY 37 subscriber.by-imsi-901990000000003.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + + + +GET 38 subscriber.by-msisdn-103.info +GET_REPLY 38 subscriber.by-msisdn-103.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +GET 39 subscriber.by-msisdn-103.info-aud +GET_REPLY 39 subscriber.by-msisdn-103.info-aud +aud2g.algo COMP128v1 +aud2g.ki 000102030405060708090a0b0c0d0e0f +aud3g.algo MILENAGE +aud3g.k 000102030405060708090a0b0c0d0e0f +aud3g.opc 101112131415161718191a1b1c1d1e1f +aud3g.ind_bitlen 5 +aud3g.sqn 2342 + +GET 40 subscriber.by-msisdn-103.info-all +GET_REPLY 40 subscriber.by-msisdn-103.info-all +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 +aud2g.algo COMP128v1 +aud2g.ki 000102030405060708090a0b0c0d0e0f +aud3g.algo MILENAGE +aud3g.k 000102030405060708090a0b0c0d0e0f +aud3g.opc 101112131415161718191a1b1c1d1e1f +aud3g.ind_bitlen 5 +aud3g.sqn 2342 + +GET 41 subscriber.by-msisdn-103.ps-enabled +GET_REPLY 41 subscriber.by-msisdn-103.ps-enabled 1 + +SET 42 subscriber.by-msisdn-103.ps-enabled 0 +SET_REPLY 42 subscriber.by-msisdn-103.ps-enabled OK +GET 43 subscriber.by-msisdn-103.ps-enabled +GET_REPLY 43 subscriber.by-msisdn-103.ps-enabled 0 + +GET 44 subscriber.by-msisdn-103.info +GET_REPLY 44 subscriber.by-msisdn-103.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 0 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 45 subscriber.by-msisdn-103.ps-enabled 0 +SET_REPLY 45 subscriber.by-msisdn-103.ps-enabled OK +GET 46 subscriber.by-msisdn-103.ps-enabled +GET_REPLY 46 subscriber.by-msisdn-103.ps-enabled 0 + +SET 47 subscriber.by-msisdn-103.ps-enabled 1 +SET_REPLY 47 subscriber.by-msisdn-103.ps-enabled OK +GET 48 subscriber.by-msisdn-103.ps-enabled +GET_REPLY 48 subscriber.by-msisdn-103.ps-enabled 1 + +GET 49 subscriber.by-msisdn-103.info +GET_REPLY 49 subscriber.by-msisdn-103.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 50 subscriber.by-msisdn-103.ps-enabled 1 +SET_REPLY 50 subscriber.by-msisdn-103.ps-enabled OK +GET 51 subscriber.by-msisdn-103.ps-enabled +GET_REPLY 51 subscriber.by-msisdn-103.ps-enabled 1 + +GET 52 subscriber.by-msisdn-103.cs-enabled +GET_REPLY 52 subscriber.by-msisdn-103.cs-enabled 1 + +SET 53 subscriber.by-msisdn-103.cs-enabled 0 +SET_REPLY 53 subscriber.by-msisdn-103.cs-enabled OK +GET 54 subscriber.by-msisdn-103.cs-enabled +GET_REPLY 54 subscriber.by-msisdn-103.cs-enabled 0 + +GET 55 subscriber.by-msisdn-103.info +GET_REPLY 55 subscriber.by-msisdn-103.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 0 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 56 subscriber.by-msisdn-103.cs-enabled 0 +SET_REPLY 56 subscriber.by-msisdn-103.cs-enabled OK +GET 57 subscriber.by-msisdn-103.cs-enabled +GET_REPLY 57 subscriber.by-msisdn-103.cs-enabled 0 + +SET 58 subscriber.by-msisdn-103.cs-enabled 1 +SET_REPLY 58 subscriber.by-msisdn-103.cs-enabled OK +GET 59 subscriber.by-msisdn-103.cs-enabled +GET_REPLY 59 subscriber.by-msisdn-103.cs-enabled 1 + +GET 60 subscriber.by-msisdn-103.info +GET_REPLY 60 subscriber.by-msisdn-103.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 61 subscriber.by-msisdn-103.cs-enabled 1 +SET_REPLY 61 subscriber.by-msisdn-103.cs-enabled OK +GET 62 subscriber.by-msisdn-103.cs-enabled +GET_REPLY 62 subscriber.by-msisdn-103.cs-enabled 1 + +SET 63 subscriber.by-msisdn-103.ps-enabled 0 +SET_REPLY 63 subscriber.by-msisdn-103.ps-enabled OK +SET 64 subscriber.by-msisdn-103.cs-enabled 0 +SET_REPLY 64 subscriber.by-msisdn-103.cs-enabled OK +GET 65 subscriber.by-msisdn-103.info +GET_REPLY 65 subscriber.by-msisdn-103.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 0 +nam_ps 0 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 66 subscriber.by-msisdn-103.ps-enabled 1 +SET_REPLY 66 subscriber.by-msisdn-103.ps-enabled OK +SET 67 subscriber.by-msisdn-103.cs-enabled 1 +SET_REPLY 67 subscriber.by-msisdn-103.cs-enabled OK +GET 68 subscriber.by-msisdn-103.info +GET_REPLY 68 subscriber.by-msisdn-103.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + + + +GET 69 subscriber.by-id-3.info +GET_REPLY 69 subscriber.by-id-3.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +GET 70 subscriber.by-id-3.info-aud +GET_REPLY 70 subscriber.by-id-3.info-aud +aud2g.algo COMP128v1 +aud2g.ki 000102030405060708090a0b0c0d0e0f +aud3g.algo MILENAGE +aud3g.k 000102030405060708090a0b0c0d0e0f +aud3g.opc 101112131415161718191a1b1c1d1e1f +aud3g.ind_bitlen 5 +aud3g.sqn 2342 + +GET 71 subscriber.by-id-3.info-all +GET_REPLY 71 subscriber.by-id-3.info-all +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 +aud2g.algo COMP128v1 +aud2g.ki 000102030405060708090a0b0c0d0e0f +aud3g.algo MILENAGE +aud3g.k 000102030405060708090a0b0c0d0e0f +aud3g.opc 101112131415161718191a1b1c1d1e1f +aud3g.ind_bitlen 5 +aud3g.sqn 2342 + +GET 72 subscriber.by-id-3.ps-enabled +GET_REPLY 72 subscriber.by-id-3.ps-enabled 1 + +SET 73 subscriber.by-id-3.ps-enabled 0 +SET_REPLY 73 subscriber.by-id-3.ps-enabled OK +GET 74 subscriber.by-id-3.ps-enabled +GET_REPLY 74 subscriber.by-id-3.ps-enabled 0 + +GET 75 subscriber.by-id-3.info +GET_REPLY 75 subscriber.by-id-3.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 0 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 76 subscriber.by-id-3.ps-enabled 0 +SET_REPLY 76 subscriber.by-id-3.ps-enabled OK +GET 77 subscriber.by-id-3.ps-enabled +GET_REPLY 77 subscriber.by-id-3.ps-enabled 0 + +SET 78 subscriber.by-id-3.ps-enabled 1 +SET_REPLY 78 subscriber.by-id-3.ps-enabled OK +GET 79 subscriber.by-id-3.ps-enabled +GET_REPLY 79 subscriber.by-id-3.ps-enabled 1 + +GET 80 subscriber.by-id-3.info +GET_REPLY 80 subscriber.by-id-3.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 81 subscriber.by-id-3.ps-enabled 1 +SET_REPLY 81 subscriber.by-id-3.ps-enabled OK +GET 82 subscriber.by-id-3.ps-enabled +GET_REPLY 82 subscriber.by-id-3.ps-enabled 1 + +GET 83 subscriber.by-id-3.cs-enabled +GET_REPLY 83 subscriber.by-id-3.cs-enabled 1 + +SET 84 subscriber.by-id-3.cs-enabled 0 +SET_REPLY 84 subscriber.by-id-3.cs-enabled OK +GET 85 subscriber.by-id-3.cs-enabled +GET_REPLY 85 subscriber.by-id-3.cs-enabled 0 + +GET 86 subscriber.by-id-3.info +GET_REPLY 86 subscriber.by-id-3.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 0 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 87 subscriber.by-id-3.cs-enabled 0 +SET_REPLY 87 subscriber.by-id-3.cs-enabled OK +GET 88 subscriber.by-id-3.cs-enabled +GET_REPLY 88 subscriber.by-id-3.cs-enabled 0 + +SET 89 subscriber.by-id-3.cs-enabled 1 +SET_REPLY 89 subscriber.by-id-3.cs-enabled OK +GET 90 subscriber.by-id-3.cs-enabled +GET_REPLY 90 subscriber.by-id-3.cs-enabled 1 + +GET 91 subscriber.by-id-3.info +GET_REPLY 91 subscriber.by-id-3.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 92 subscriber.by-id-3.cs-enabled 1 +SET_REPLY 92 subscriber.by-id-3.cs-enabled OK +GET 93 subscriber.by-id-3.cs-enabled +GET_REPLY 93 subscriber.by-id-3.cs-enabled 1 + +SET 94 subscriber.by-id-3.ps-enabled 0 +SET_REPLY 94 subscriber.by-id-3.ps-enabled OK +SET 95 subscriber.by-id-3.cs-enabled 0 +SET_REPLY 95 subscriber.by-id-3.cs-enabled OK +GET 96 subscriber.by-id-3.info +GET_REPLY 96 subscriber.by-id-3.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 0 +nam_ps 0 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +SET 97 subscriber.by-id-3.ps-enabled 1 +SET_REPLY 97 subscriber.by-id-3.ps-enabled OK +SET 98 subscriber.by-id-3.cs-enabled 1 +SET_REPLY 98 subscriber.by-id-3.cs-enabled OK +GET 99 subscriber.by-id-3.info +GET_REPLY 99 subscriber.by-id-3.info +id 3 +imsi 901990000000003 +msisdn 103 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +GET 100 subscriber.by-id-00123.info +GET_REPLY 100 subscriber.by-id-00123.info +id 123 +imsi 123123 +msisdn 123 +nam_cs 1 +nam_ps 1 +ms_purged_cs 0 +ms_purged_ps 0 +periodic_lu_timer 0 +periodic_rau_tau_timer 0 +lmsi 00000000 + +GET 101 subscriber.by-id-0x0123.info +ERROR 101 Invalid value part of 'by-xxx-value' selector. diff --git a/tests/test_subscriber.sql b/tests/test_subscriber.sql new file mode 100644 index 0000000..2b6afac --- /dev/null +++ b/tests/test_subscriber.sql @@ -0,0 +1,17 @@ + +-- 2G only subscriber +INSERT INTO subscriber (id, imsi, msisdn) VALUES (1, '901990000000001', '1'); +INSERT INTO auc_2g (subscriber_id, algo_id_2g, ki) VALUES (1, 1, '000102030405060708090a0b0c0d0e0f'); + +-- 3G only subscriber +INSERT INTO subscriber (id, imsi) VALUES (2, '901990000000002'); +INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, opc, sqn) VALUES (2, 5, '000102030405060708090a0b0c0d0e0f', '101112131415161718191a1b1c1d1e1f', 4223); + +-- 2G + 3G subscriber +INSERT INTO subscriber (id, imsi, msisdn) VALUES (3, '901990000000003', '103'); +INSERT INTO auc_2g (subscriber_id, algo_id_2g, ki) VALUES (3, 1, '000102030405060708090a0b0c0d0e0f'); +INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, opc, sqn) VALUES (3, 5, '000102030405060708090a0b0c0d0e0f', '101112131415161718191a1b1c1d1e1f', 2342); + +-- A subscriber id > 7 and > 15 to check against octal and hex notations +INSERT INTO subscriber (id, imsi, msisdn) VALUES (123, '123123', '123'); +INSERT INTO auc_2g (subscriber_id, algo_id_2g, ki) VALUES (123, 3, 'BeefedCafeFaceAcedAddedDecadeFee'); diff --git a/tests/test_subscriber.vty b/tests/test_subscriber.vty new file mode 100644 index 0000000..2da455f --- /dev/null +++ b/tests/test_subscriber.vty @@ -0,0 +1,349 @@ +OsmoHLR> enable + +OsmoHLR# list +... + subscriber (imsi|msisdn|id) IDENT show + subscriber imsi IDENT create + subscriber (imsi|msisdn|id) IDENT delete + subscriber (imsi|msisdn|id) IDENT update msisdn MSISDN + subscriber (imsi|msisdn|id) IDENT update aud2g none + subscriber (imsi|msisdn|id) IDENT update aud2g (comp128v1|comp128v2|comp128v3|xor) ki KI + subscriber (imsi|msisdn|id) IDENT update aud3g none + subscriber (imsi|msisdn|id) IDENT update aud3g milenage k K (op|opc) OP_C [ind-bitlen] [<0-28>] + +OsmoHLR# subscriber? + subscriber Subscriber management commands + +OsmoHLR# subscriber ? + imsi Identify subscriber by IMSI + msisdn Identify subscriber by MSISDN (phone number) + id Identify subscriber by database ID + +OsmoHLR# subscriber imsi ? + IDENT IMSI/MSISDN/ID of the subscriber +OsmoHLR# subscriber msisdn ? + IDENT IMSI/MSISDN/ID of the subscriber +OsmoHLR# subscriber id ? + IDENT IMSI/MSISDN/ID of the subscriber + +OsmoHLR# subscriber imsi 123456789023000 show +% No subscriber for imsi = '123456789023000' +OsmoHLR# subscriber id 1 show +% No subscriber for id = '1' +OsmoHLR# subscriber msisdn 12345 show +% No subscriber for msisdn = '12345' + +OsmoHLR# subscriber imsi 1234567890230001 create +% Not a valid IMSI: 1234567890230001 +OsmoHLR# subscriber imsi 12345678902300x create +% Not a valid IMSI: 12345678902300x +OsmoHLR# subscriber imsi 12345 create +% Not a valid IMSI: 12345 + +OsmoHLR# subscriber imsi 123456789023000 create +% Created subscriber 123456789023000 + ID: 1 + IMSI: 123456789023000 + MSISDN: none + +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: none +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: none +OsmoHLR# subscriber msisdn 12345 show +% No subscriber for msisdn = '12345' + +OsmoHLR# subscriber imsi 123456789023000 update msisdn 12345 +% Updated subscriber IMSI='123456789023000' to MSISDN='12345' + +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 12345 +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 12345 +OsmoHLR# subscriber msisdn 12345 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 12345 + +OsmoHLR# subscriber msisdn 12345 update msisdn 423 +% Updated subscriber IMSI='123456789023000' to MSISDN='423' +OsmoHLR# subscriber msisdn 12345 show +% No subscriber for msisdn = '12345' + +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 +OsmoHLR# subscriber msisdn 423 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + +OsmoHLR# subscriber imsi 123456789023000 update ? + msisdn Set MSISDN (phone number) of the subscriber + aud2g Set 2G authentication data + aud3g Set UMTS authentication data (3G, and 2G with UMTS AKA) + +OsmoHLR# subscriber imsi 123456789023000 update aud2g ? + none Delete 2G authentication data + comp128v1 Use COMP128v1 algorithm + comp128v2 Use COMP128v2 algorithm + comp128v3 Use COMP128v3 algorithm + xor Use XOR algorithm + +OsmoHLR# subscriber imsi 123456789023000 update aud2g comp128v1 ? + ki Set Ki Encryption Key + +OsmoHLR# subscriber imsi 123456789023000 update aud2g comp128v1 ki ? + KI Ki as 32 hexadecimal characters + +OsmoHLR# subscriber imsi 123456789023000 update aud2g comp128v1 ki val ? + + +OsmoHLR# subscriber imsi 123456789023000 update aud2g xor ki Deaf0ff1ceD0d0DabbedD1ced1ceF00d +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: XOR + KI=deaf0ff1ced0d0dabbedd1ced1cef00d + +OsmoHLR# subscriber imsi 123456789023000 update aud2g comp128v1 ki BeefedCafeFaceAcedAddedDecadeFee +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v1 + KI=beefedcafefaceacedaddeddecadefee +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v1 + KI=beefedcafefaceacedaddeddecadefee +OsmoHLR# subscriber msisdn 423 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v1 + KI=beefedcafefaceacedaddeddecadefee + +OsmoHLR# subscriber id 1 update aud2g comp128v2 ki CededEffacedAceFacedBadFadedBeef +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v2 + KI=cededeffacedacefacedbadfadedbeef +OsmoHLR# subscriber msisdn 423 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v2 + KI=cededeffacedacefacedbadfadedbeef +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v2 + KI=cededeffacedacefacedbadfadedbeef + +OsmoHLR# subscriber msisdn 423 update aud2g comp128v3 ki C01ffedC1cadaeAc1d1f1edAcac1aB0a +OsmoHLR# subscriber msisdn 423 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v3 + KI=c01ffedc1cadaeac1d1f1edacac1ab0a +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v3 + KI=c01ffedc1cadaeac1d1f1edacac1ab0a +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v3 + KI=c01ffedc1cadaeac1d1f1edacac1ab0a + +OsmoHLR# subscriber id 1 update aud2g nonsense ki BeefedCafeFaceAcedAddedDecadeFee +% Unknown command. +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v3 + KI=c01ffedc1cadaeac1d1f1edacac1ab0a + +OsmoHLR# subscriber id 1 update aud2g milenage ki BeefedCafeFaceAcedAddedDecadeFee +% Unknown command. +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v3 + KI=c01ffedc1cadaeac1d1f1edacac1ab0a + +OsmoHLR# subscriber id 1 update aud2g xor ki CoiffedCicadaeAcidifiedAcaciaBoa +% Invalid value for KI: 'CoiffedCicadaeAcidifiedAcaciaBoa' +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v3 + KI=c01ffedc1cadaeac1d1f1edacac1ab0a + +OsmoHLR# subscriber id 1 update aud2g xor ki C01ffedC1cadaeAc1d1f1edAcac1aB0aX +% Invalid value for KI: 'C01ffedC1cadaeAc1d1f1edAcac1aB0aX' +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v3 + KI=c01ffedc1cadaeac1d1f1edacac1ab0a + +OsmoHLR# subscriber id 1 update aud2g none +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + + +OsmoHLR# subscriber imsi 123456789023000 update aud3g ? + none Delete 3G authentication data + milenage Use Milenage algorithm + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage ? + k Set Encryption Key K + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k ? + K K as 32 hexadecimal characters + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d ? + op Set OP key + opc Set OPC key + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc ? + OP_C OP or OPC as 32 hexadecimal characters + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CededEffacedAceFacedBadFadedBeef ? + [ind-bitlen] Set IND bit length + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CededEffacedAceFacedBadFadedBeef ind-bitlen ? + [<0-28>] IND bit length value (default: 5) + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CededEffacedAceFacedBadFadedBeef +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 3G auth: MILENAGE + K=deaf0ff1ced0d0dabbedd1ced1cef00d + OPC=cededeffacedacefacedbadfadedbeef + IND-bitlen=5 + + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d op DeafBeddedBabeAcceededFadedDecaf +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 3G auth: MILENAGE + K=deaf0ff1ced0d0dabbedd1ced1cef00d + OP=deafbeddedbabeacceededfadeddecaf + IND-bitlen=5 + +OsmoHLR# subscriber imsi 123456789023000 update aud3g none +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CededEffacedAceFacedBadFadedBeef ind-bitlen 23 +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 3G auth: MILENAGE + K=deaf0ff1ced0d0dabbedd1ced1cef00d + OPC=cededeffacedacefacedbadfadedbeef + IND-bitlen=23 + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k CoiffedCicadaeAcidifiedAcaciaBoa opc CededEffacedAceFacedBadFadedBeef +% Invalid value for K: 'CoiffedCicadaeAcidifiedAcaciaBoa' +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 3G auth: MILENAGE + K=deaf0ff1ced0d0dabbedd1ced1cef00d + OPC=cededeffacedacefacedbadfadedbeef + IND-bitlen=23 + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d opc CoiffedCicadaeAcidifiedAcaciaBoa +% Invalid value for OPC: 'CoiffedCicadaeAcidifiedAcaciaBoa' +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 3G auth: MILENAGE + K=deaf0ff1ced0d0dabbedd1ced1cef00d + OPC=cededeffacedacefacedbadfadedbeef + IND-bitlen=23 + +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d op C01ffedC1cadaeAc1d1f1edAcac1aB0a +OsmoHLR# subscriber imsi 123456789023000 update aud3g milenage k Deaf0ff1ceD0d0DabbedD1ced1ceF00d op CoiffedCicadaeAcidifiedAcaciaBoa +% Invalid value for OP: 'CoiffedCicadaeAcidifiedAcaciaBoa' +OsmoHLR# subscriber imsi 123456789023000 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 3G auth: MILENAGE + K=deaf0ff1ced0d0dabbedd1ced1cef00d + OP=c01ffedc1cadaeac1d1f1edacac1ab0a + IND-bitlen=5 + +OsmoHLR# subscriber id 1 update aud2g comp128v2 ki CededEffacedAceFacedBadFadedBeef +OsmoHLR# subscriber id 1 show + ID: 1 + IMSI: 123456789023000 + MSISDN: 423 + 2G auth: COMP128v2 + KI=cededeffacedacefacedbadfadedbeef + 3G auth: MILENAGE + K=deaf0ff1ced0d0dabbedd1ced1cef00d + OP=c01ffedc1cadaeac1d1f1edacac1ab0a + IND-bitlen=5 + +OsmoHLR# subscriber imsi 123456789023000 delete +% Deleted subscriber for IMSI '123456789023000' + +OsmoHLR# subscriber imsi 123456789023000 show +% No subscriber for imsi = '123456789023000' +OsmoHLR# subscriber id 1 show +% No subscriber for id = '1' +OsmoHLR# subscriber msisdn 423 show +% No subscriber for msisdn = '423' + +OsmoHLR# subscriber imsi 123456789023000 create +% Created subscriber 123456789023000 + ID: 1 + IMSI: 123456789023000 + MSISDN: none + +OsmoHLR# subscriber imsi 123456789023000 delete +% Deleted subscriber for IMSI '123456789023000' diff --git a/tests/test_subscriber_errors.ctrl b/tests/test_subscriber_errors.ctrl new file mode 100644 index 0000000..2f64fdb --- /dev/null +++ b/tests/test_subscriber_errors.ctrl @@ -0,0 +1,107 @@ +GET 1 invalid +ERROR 1 Command not found +SET 2 invalid nonsense +ERROR 2 Command not found + +GET 3 subscriber.by-imsi-nonsense.info +ERROR 3 Invalid value part of 'by-xxx-value' selector. +GET 4 subscriber.by-msisdn-nonsense.info +ERROR 4 Invalid value part of 'by-xxx-value' selector. +GET 5 subscriber.by-id-nonsense.info +ERROR 5 Invalid value part of 'by-xxx-value' selector. + +GET 6 subscriber +ERROR 6 Command not present. +GET 7 subscriber. +ERROR 7 Command not present. +GET 8 subscriber.by-nonsense +ERROR 8 Command not present. +GET 9 subscriber.by-nonsense- +ERROR 9 Command not present. +GET 10 subscriber.by-nonsense-123456 +ERROR 10 Command not present. +GET 11 subscriber.by-nonsense-123456. +ERROR 11 Command not present. +GET 12 subscriber.by-imsi- +ERROR 12 Command not present. +GET 13 subscriber.by-imsi-. +ERROR 13 Command not present. +GET 14 subscriber.by-imsi-901990000000003 +ERROR 14 Command not present. +GET 15 subscriber.by-imsi-901990000000003. +ERROR 15 Command not present. + +GET 16 subscriber.by-nonsense-123456.info +ERROR 16 Not a known subscriber 'by-xxx-' selector. +GET 17 subscriber.by-123456.info +ERROR 17 Not a known subscriber 'by-xxx-' selector. + +GET 18 subscriber.by-imsi-.info +ERROR 18 Invalid value part of 'by-xxx-value' selector. +GET 19 subscriber.by-imsi--.info +ERROR 19 Invalid value part of 'by-xxx-value' selector. + +GET 20 subscriber.by-imsi-12345678901234567.info +ERROR 20 Invalid value part of 'by-xxx-value' selector. +GET 21 subscriber.by-imsi-12345.info +ERROR 21 Invalid value part of 'by-xxx-value' selector. +GET 22 subscriber.by-imsi-1234567890123456.info +ERROR 22 Invalid value part of 'by-xxx-value' selector. + +GET 23 subscriber.by-id-99999999999999999999999999.info +ERROR 23 Invalid value part of 'by-xxx-value' selector. +GET 24 subscriber.by-id-9223372036854775807.info +ERROR 24 No such subscriber. +GET 25 subscriber.by-id-9223372036854775808.info +ERROR 25 Invalid value part of 'by-xxx-value' selector. +GET 26 subscriber.by-id--1.info +ERROR 26 No such subscriber. +GET 27 subscriber.by-id--9223372036854775808.info +ERROR 27 No such subscriber. +GET 28 subscriber.by-id--9223372036854775809.info +ERROR 28 Invalid value part of 'by-xxx-value' selector. + +GET 29 subscriber.by-id-1+1.info +ERROR 29 Invalid value part of 'by-xxx-value' selector. +GET 30 subscriber.by-id--.info +ERROR 30 Invalid value part of 'by-xxx-value' selector. +GET 31 subscriber.by-id-+1.info +ERROR 31 Invalid value part of 'by-xxx-value' selector. +GET 32 subscriber.by-id-+-1.info +ERROR 32 Invalid value part of 'by-xxx-value' selector. +GET 33 subscriber.by-id--+1.info +ERROR 33 Invalid value part of 'by-xxx-value' selector. +GET 34 subscriber.by-id-++1.info +ERROR 34 Invalid value part of 'by-xxx-value' selector. +GET 35 subscriber.by-id---1.info +ERROR 35 Invalid value part of 'by-xxx-value' selector. + +GET 36 subscriber.by-id- 1.info +ERROR 36 Command not present. +GET 37 subscriber.by-id-+ 1.info +ERROR 37 Command not present. +GET 38 subscriber.by-id-- 1.info +ERROR 38 Command not present. + + +SET 39 subscriber.by-imsi-901990000000001.info foo +ERROR 39 Read Only attribute +SET 40 subscriber.by-imsi-901990000000001.info-aud foo +ERROR 40 Read Only attribute +SET 41 subscriber.by-imsi-901990000000001.info-all foo +ERROR 41 Read Only attribute + +SET 42 subscriber.by-imsi-901990000000001.ps-enabled nonsense +ERROR 42 Value failed verification. +SET 43 subscriber.by-imsi-901990000000001.cs-enabled nonsense +ERROR 43 Value failed verification. + +SET 44 subscriber.by-imsi-901990000000001.ps-enabled +ERROR err Command parser error. +SET 45 subscriber.by-imsi-901990000000001.cs-enabled +ERROR err Command parser error. + +GET 46 subscriber.by-imsi-1234567890123456.ps-enabled +ERROR 46 Invalid value part of 'by-xxx-value' selector. +GET 47 subscriber.by-imsi-1234567890123456.cs-enabled +ERROR 47 Invalid value part of 'by-xxx-value' selector. diff --git a/tests/testsuite.at b/tests/testsuite.at new file mode 100644 index 0000000..74179e7 --- /dev/null +++ b/tests/testsuite.at @@ -0,0 +1,31 @@ +AT_INIT +AT_BANNER([Regression tests.]) + +AT_SETUP([auc]) +AT_KEYWORDS([auc]) +cat $abs_srcdir/auc/auc_test.ok > expout +cat $abs_srcdir/auc/auc_test.err > experr +AT_CHECK([$abs_top_builddir/tests/auc/auc_test], [], [expout], [experr]) +AT_CLEANUP + +AT_SETUP([auc_ts_55_205_test_sets]) +AT_KEYWORDS([auc_ts_55_205_test_sets]) +cat $abs_srcdir/auc/auc_ts_55_205_test_sets.ok > expout +cat $abs_srcdir/auc/auc_ts_55_205_test_sets.err > experr +AT_CHECK([$abs_top_builddir/tests/auc/auc_ts_55_205_test_sets], [], [expout], [experr]) +AT_CLEANUP + +AT_SETUP([gsup_server]) +AT_KEYWORDS([gsup_server]) +cat $abs_srcdir/gsup_server/gsup_server_test.ok > expout +cat $abs_srcdir/gsup_server/gsup_server_test.err > experr +AT_CHECK([$abs_top_builddir/tests/gsup_server/gsup_server_test], [], [expout], [experr]) +AT_CLEANUP + +AT_SETUP([db]) +AT_KEYWORDS([db]) +cat $abs_srcdir/db/db_test.ok > expout +cat $abs_srcdir/db/db_test.err > experr +sqlite3 db_test.db < $abs_top_srcdir/sql/hlr.sql +AT_CHECK([$abs_top_builddir/tests/db/db_test], [], [expout], [experr]) +AT_CLEANUP -- cgit v1.2.3 From 827de784103fab1cea86fa19ab117527bffd53e3 Mon Sep 17 00:00:00 2001 From: Thorsten Alteholz Date: Thu, 19 Apr 2018 19:07:49 +0200 Subject: Import osmo-hlr_0.1.0-3.debian.tar.xz [dgit import tarball osmo-hlr 0.1.0-3 osmo-hlr_0.1.0-3.debian.tar.xz] --- changelog | 21 +++++++++++++++++++++ compat | 1 + control | 25 +++++++++++++++++++++++++ copyright | 40 ++++++++++++++++++++++++++++++++++++++++ osmo-hlr.install | 5 +++++ osmo-hlr.service | 1 + osmo-hlr.service.ooo | 12 ++++++++++++ patches/series | 2 ++ patches/service.patch | 13 +++++++++++++ patches/spelling.patch | 15 +++++++++++++++ rules | 27 +++++++++++++++++++++++++++ source/format | 1 + watch | 2 ++ 13 files changed, 165 insertions(+) create mode 100644 changelog create mode 100644 compat create mode 100644 control create mode 100644 copyright create mode 100644 osmo-hlr.install create mode 120000 osmo-hlr.service create mode 100644 osmo-hlr.service.ooo create mode 100644 patches/series create mode 100644 patches/service.patch create mode 100644 patches/spelling.patch create mode 100755 rules create mode 100644 source/format create mode 100644 watch diff --git a/changelog b/changelog new file mode 100644 index 0000000..3befad4 --- /dev/null +++ b/changelog @@ -0,0 +1,21 @@ +osmo-hlr (0.1.0-3) unstable; urgency=medium + + * debian/rules: deactivate tests for BE for now + + -- Thorsten Alteholz Thu, 19 Apr 2018 19:07:49 +0200 + +osmo-hlr (0.1.0-2) unstable; urgency=medium + + * move to unstable + * debian/control: add salsa URLs + * debian/control: use dh11 + * debian/control: bump standard to 4.1.4 (no changes) + * debian/control: move package to debian-mobcom + + -- Thorsten Alteholz Mon, 16 Apr 2018 19:05:39 +0200 + +osmo-hlr (0.1.0-1) experimental; urgency=medium + + * Initial release + + -- Thorsten Alteholz Wed, 13 Dec 2017 19:05:39 +0100 diff --git a/compat b/compat new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/compat @@ -0,0 +1 @@ +11 diff --git a/control b/control new file mode 100644 index 0000000..e6fed73 --- /dev/null +++ b/control @@ -0,0 +1,25 @@ +Source: osmo-hlr +Section: net +Priority: optional +Maintainer: Debian Mobcom Maintainers +Uploaders: Thorsten Alteholz +Build-Depends: debhelper (>= 11), + pkg-config, + python-minimal, + libosmocore-dev (>= 0.10.1), + libosmo-abis-dev (>= 0.4.0), + libosmo-netif-dev (>= 0.1.1), + libsqlite3-dev, + sqlite3 +Standards-Version: 4.1.4 +Vcs-Browser: https://salsa.debian.org/debian-mobcom-team/osmo-hlr +Vcs-Git: https://salsa.debian.org/debian-mobcom-team/osmo-hlr.git +Homepage: https://projects.osmocom.org/projects/osmo-hlr + +Package: osmo-hlr +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, libdbd-sqlite3 +Description: Osmocom Home Location Register + OsmoHLR is a Osmocom implementation of HLR (Home Location Registrar) + which works over GSUP protocol. The subscribers are store in sqlite DB. + It supports both 2G and 3G authentication. diff --git a/copyright b/copyright new file mode 100644 index 0000000..955c2f9 --- /dev/null +++ b/copyright @@ -0,0 +1,40 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: OsmoHLR +Source: http://cgit.osmocom.org/osmo-hlr/ +Files-Excluded: debian + +Files: * +Copyright: 2016-2017 Sysmocom s. f. m. c. GmbH +License: AGPL-3+ + +Files: contrib/ipa.py +Copyright: 2016-2017 Sysmocom s. f. m. c. GmbH +License: GPL-3+ + +License: AGPL-3+ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + . + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +License: GPL-3+ + 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 3 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. + . + The complete text of the GNU General Public License version 3 + can be found in `/usr/share/common-licenses/GPL-3'. diff --git a/osmo-hlr.install b/osmo-hlr.install new file mode 100644 index 0000000..d0c13f5 --- /dev/null +++ b/osmo-hlr.install @@ -0,0 +1,5 @@ +#/usr/bin/osmo-hlr +#/usr/bin/osmo-hlr-db-tool +#/usr/share/doc/osmo-hlr/hlr.sql +#/usr/share/doc/osmo-hlr/examples/osmo-hlr.cfg +#/usr/share/doc/osmo-hlr/examples/osmo-hlr.cfg /etc/osmocom/ diff --git a/osmo-hlr.service b/osmo-hlr.service new file mode 120000 index 0000000..184f5aa --- /dev/null +++ b/osmo-hlr.service @@ -0,0 +1 @@ +../contrib/systemd/osmo-hlr.service \ No newline at end of file diff --git a/osmo-hlr.service.ooo b/osmo-hlr.service.ooo new file mode 100644 index 0000000..3ae1d4c --- /dev/null +++ b/osmo-hlr.service.ooo @@ -0,0 +1,12 @@ +[Unit] +Description=Osmocom Home Location Register (OsmoHLR) +Documentation=https://projects.osmocom.org/projects/osmo-hlr + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/osmo-hlr -c /etc/osmocom/osmo-hlr.cfg -l /var/lib/osmocom/hlr.db +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/patches/series b/patches/series new file mode 100644 index 0000000..8444c82 --- /dev/null +++ b/patches/series @@ -0,0 +1,2 @@ +spelling.patch +service.patch diff --git a/patches/service.patch b/patches/service.patch new file mode 100644 index 0000000..ae90db7 --- /dev/null +++ b/patches/service.patch @@ -0,0 +1,13 @@ +Description: add documentation to silence lintian +Author: Thorsten Alteholz +Index: osmo-hlr-0.1.0/contrib/systemd/osmo-hlr.service +=================================================================== +--- osmo-hlr-0.1.0.orig/contrib/systemd/osmo-hlr.service 2017-12-13 11:31:08.099273602 +0100 ++++ osmo-hlr-0.1.0/contrib/systemd/osmo-hlr.service 2017-12-13 11:31:49.725604667 +0100 +@@ -1,5 +1,6 @@ + [Unit] + Description=Osmocom Home Location Register (OsmoHLR) ++Documentation=https://projects.osmocom.org/projects/osmo-hlr + + [Service] + Type=simple diff --git a/patches/spelling.patch b/patches/spelling.patch new file mode 100644 index 0000000..e8ae726 --- /dev/null +++ b/patches/spelling.patch @@ -0,0 +1,15 @@ +Description: fix spelling errors detected by lintian +Author: Thorsten Alteholz +Index: osmo-hlr-0.1.0/src/ctrl.c +=================================================================== +--- osmo-hlr-0.1.0.orig/src/ctrl.c 2017-10-28 20:43:12.000000000 +0200 ++++ osmo-hlr-0.1.0/src/ctrl.c 2017-12-13 13:09:31.053977442 +0100 +@@ -95,7 +95,7 @@ + cmd->reply = "No such subscriber."; + return false; + default: +- cmd->reply = "An unknown error has occured during get_subscriber()."; ++ cmd->reply = "An unknown error has occurred during get_subscriber()."; + return false; + } + } diff --git a/rules b/rules new file mode 100755 index 0000000..1527420 --- /dev/null +++ b/rules @@ -0,0 +1,27 @@ +#!/usr/bin/make -f + +#export DH_VERBOSE=1 +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +arch = $(shell dpkg-architecture -qDEB_BUILD_ARCH) + +%: + dh $@ --with autoreconf + +override_dh_shlibdeps: + dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info + +# Print test results in case of a failure +override_dh_auto_test: + echo ${arch} + if [ "${arch}" = "s390x" ] || \ + [ "${arch}" = "hppa" ] || \ + [ "${arch}" = "powerpc" ] || \ + [ "${arch}" = "ppc64" ] || \ + [ "${arch}" = "sparc64" ] || \ + [ "${arch}" = "mips" ] ; then \ + echo "Do not care of test result on this architecture" ;\ + else \ + echo "Do make tests on this architecture" ;\ + dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false) \ + fi diff --git a/source/format b/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/watch b/watch new file mode 100644 index 0000000..086f508 --- /dev/null +++ b/watch @@ -0,0 +1,2 @@ +version=4 +opts="mode=git, dversionmangle=s/\+ds//" https://git.osmocom.org/osmo-hlr refs/tags/([\d\.]+) debian uupdate -- cgit v1.2.3 From 770794cbec29768453ac513a5b898892b30b5ad2 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Thu, 19 Apr 2018 19:07:49 +0200 Subject: spelling =================================================================== Gbp-Pq: Name spelling.patch --- src/ctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctrl.c b/src/ctrl.c index 3e81661..8df8a10 100644 --- a/src/ctrl.c +++ b/src/ctrl.c @@ -95,7 +95,7 @@ static bool get_subscriber(struct db_context *dbc, cmd->reply = "No such subscriber."; return false; default: - cmd->reply = "An unknown error has occured during get_subscriber()."; + cmd->reply = "An unknown error has occurred during get_subscriber()."; return false; } } -- cgit v1.2.3 From 29b5469dc797333bf0164d07a1830d20dc55e927 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Thu, 19 Apr 2018 19:07:49 +0200 Subject: service =================================================================== Gbp-Pq: Name service.patch --- contrib/systemd/osmo-hlr.service | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/systemd/osmo-hlr.service b/contrib/systemd/osmo-hlr.service index 64e369d..3ae1d4c 100644 --- a/contrib/systemd/osmo-hlr.service +++ b/contrib/systemd/osmo-hlr.service @@ -1,5 +1,6 @@ [Unit] Description=Osmocom Home Location Register (OsmoHLR) +Documentation=https://projects.osmocom.org/projects/osmo-hlr [Service] Type=simple -- cgit v1.2.3 From 5239f6ccb82ac905d4e4a5458d581f541fb2d875 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Tue, 6 Nov 2018 07:54:48 +0100 Subject: spelling =================================================================== Gbp-Pq: Name spelling.patch --- src/ctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctrl.c b/src/ctrl.c index 8ae9d7c..aa0b7d2 100644 --- a/src/ctrl.c +++ b/src/ctrl.c @@ -95,7 +95,7 @@ static bool get_subscriber(struct db_context *dbc, cmd->reply = "No such subscriber."; return false; default: - cmd->reply = "An unknown error has occured during get_subscriber()."; + cmd->reply = "An unknown error has occurred during get_subscriber()."; return false; } } -- cgit v1.2.3 From f5736664f1c0aceba95e203def5b3065d5a52462 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Tue, 6 Nov 2018 07:54:48 +0100 Subject: service =================================================================== Gbp-Pq: Name service.patch --- contrib/systemd/osmo-hlr.service | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/systemd/osmo-hlr.service b/contrib/systemd/osmo-hlr.service index 64e369d..3ae1d4c 100644 --- a/contrib/systemd/osmo-hlr.service +++ b/contrib/systemd/osmo-hlr.service @@ -1,5 +1,6 @@ [Unit] Description=Osmocom Home Location Register (OsmoHLR) +Documentation=https://projects.osmocom.org/projects/osmo-hlr [Service] Type=simple -- cgit v1.2.3 From fe62198ff9eda552cdb4bf364c3359cc8bd92e73 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Fri, 9 Nov 2018 22:19:28 +0100 Subject: spelling =================================================================== Gbp-Pq: Name spelling.patch --- src/ctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctrl.c b/src/ctrl.c index 8ae9d7c..aa0b7d2 100644 --- a/src/ctrl.c +++ b/src/ctrl.c @@ -95,7 +95,7 @@ static bool get_subscriber(struct db_context *dbc, cmd->reply = "No such subscriber."; return false; default: - cmd->reply = "An unknown error has occured during get_subscriber()."; + cmd->reply = "An unknown error has occurred during get_subscriber()."; return false; } } -- cgit v1.2.3 From b4a6eb37a9599196d15bfe0715bc1a5f776fc750 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Fri, 9 Nov 2018 22:19:28 +0100 Subject: service =================================================================== Gbp-Pq: Name service.patch --- contrib/systemd/osmo-hlr.service | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/systemd/osmo-hlr.service b/contrib/systemd/osmo-hlr.service index 64e369d..3ae1d4c 100644 --- a/contrib/systemd/osmo-hlr.service +++ b/contrib/systemd/osmo-hlr.service @@ -1,5 +1,6 @@ [Unit] Description=Osmocom Home Location Register (OsmoHLR) +Documentation=https://projects.osmocom.org/projects/osmo-hlr [Service] Type=simple -- cgit v1.2.3 From 4bf42d05f98b261319365a965cd72fd8e825c334 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Fri, 16 Nov 2018 08:38:18 +0100 Subject: spelling =================================================================== Gbp-Pq: Name spelling.patch --- src/ctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctrl.c b/src/ctrl.c index 8ae9d7c..aa0b7d2 100644 --- a/src/ctrl.c +++ b/src/ctrl.c @@ -95,7 +95,7 @@ static bool get_subscriber(struct db_context *dbc, cmd->reply = "No such subscriber."; return false; default: - cmd->reply = "An unknown error has occured during get_subscriber()."; + cmd->reply = "An unknown error has occurred during get_subscriber()."; return false; } } -- cgit v1.2.3 From a82319cc2bb248c9bb94f249601b5c8f5a35f26f Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Fri, 16 Nov 2018 08:38:18 +0100 Subject: service =================================================================== Gbp-Pq: Name service.patch --- contrib/systemd/osmo-hlr.service | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/systemd/osmo-hlr.service b/contrib/systemd/osmo-hlr.service index 64e369d..3ae1d4c 100644 --- a/contrib/systemd/osmo-hlr.service +++ b/contrib/systemd/osmo-hlr.service @@ -1,5 +1,6 @@ [Unit] Description=Osmocom Home Location Register (OsmoHLR) +Documentation=https://projects.osmocom.org/projects/osmo-hlr [Service] Type=simple -- cgit v1.2.3 From c522ad9c97fe3abfb13142311d5bf4413990d0f1 Mon Sep 17 00:00:00 2001 From: Ruben Undheim Date: Fri, 16 Nov 2018 08:47:26 +0100 Subject: Fix test for return codes on mipsel and alpha archs Gbp-Pq: Name 0003-Fix-test-for-return-codes-on-mipsel-and-alpha-archs.patch --- tests/db/db_test.c | 14 ++++++++++++-- tests/db/db_test.err | 30 +++++++++++++++--------------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/tests/db/db_test.c b/tests/db/db_test.c index 058588b..ea1a990 100644 --- a/tests/db/db_test.c +++ b/tests/db/db_test.c @@ -51,7 +51,12 @@ static void _fill_invalid(void *dest, size_t size) * The return code is then available in g_rc. */ #define ASSERT_RC(call, expect_rc) \ do { \ - fprintf(stderr, #call " --> " #expect_rc "\n"); \ + if ((expect_rc) == -ENOKEY) \ + fprintf(stderr, #call " --> -ENOKEY\n"); \ + else if ((expect_rc) == -ENOTSUP) \ + fprintf(stderr, #call " --> -ENOTSUP\n"); \ + else \ + fprintf(stderr, #call " --> " #expect_rc "\n"); \ g_rc = call; \ if (g_rc != (expect_rc)) \ fprintf(stderr, " MISMATCH: got rc = %d, expected: " \ @@ -67,7 +72,12 @@ static void _fill_invalid(void *dest, size_t size) do { \ int rc; \ fill_invalid(g_subscr); \ - fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> " \ + if ((expect_rc) == -ENOKEY) \ + fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> -ENOKEY \n"); \ + else if ((expect_rc) == -ENOTSUP) \ + fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> -ENOTSUP \n"); \ + else \ + fprintf(stderr, "db_subscr_get_by_" #by "(dbc, " #val ", &g_subscr) --> " \ #expect_rc "\n"); \ rc = db_subscr_get_by_##by(dbc, val, &g_subscr); \ if (rc != (expect_rc)) \ diff --git a/tests/db/db_test.err b/tests/db/db_test.err index 1d34045..b7913eb 100644 --- a/tests/db/db_test.err +++ b/tests/db/db_test.err @@ -729,12 +729,12 @@ struct hlr_subscriber { .imsi = '123456789000000', } -db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -126 +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data -db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -126 +db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data @@ -811,12 +811,12 @@ DAUC IMSI='123456789000000': No 3G Auth Data db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_NONE, NULL)) --> 0 -db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -126 +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data -db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -126 +db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data @@ -836,12 +836,12 @@ DAUC IMSI='123456789000000': No 3G Auth Data db_subscr_update_aud_by_id(dbc, id, mk_aud_2g(OSMO_AUTH_ALG_NONE, "f000000000000f00000000000f000000")) --> 0 -db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -126 +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data -db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -126 +db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data @@ -938,12 +938,12 @@ DAUC IMSI='123456789000000': No 2G Auth Data db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_NONE, NULL, false, NULL, 0)) --> 0 -db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -126 +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data -db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -126 +db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data @@ -973,12 +973,12 @@ DAUC IMSI='123456789000000': Updating SQN=0 in DB db_subscr_update_aud_by_id(dbc, id, mk_aud_3g(OSMO_AUTH_ALG_NONE, "asdfasdfasd", false, "asdfasdfasdf", 99999)) --> 0 -db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -126 +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data -db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -126 +db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data @@ -1219,12 +1219,12 @@ struct hlr_subscriber { .imsi = '123456789000000', } -db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -126 +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data -db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -126 +db_get_auc(dbc, imsi0, 3, vec, N_VECTORS, NULL, NULL) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data @@ -1266,7 +1266,7 @@ struct hlr_subscriber { .imsi = '123456789000000', } -db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -126 +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data @@ -1277,7 +1277,7 @@ DAUC IMSI='123456789000000': No 3G Auth Data db_update_sqn(dbc, id, 123) --> -ENOENT DAUC Cannot update SQN for subscriber ID=1: no auc_3g entry for such subscriber -db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -126 +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data @@ -1285,7 +1285,7 @@ DAUC IMSI='123456789000000': No 3G Auth Data db_update_sqn(dbc, id, 543) --> -ENOENT DAUC Cannot update SQN for subscriber ID=1: no auc_3g entry for such subscriber -db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -126 +db_get_auth_data(dbc, imsi0, &g_aud2g, &g_aud3g, &g_id) --> -ENOKEY DAUC IMSI='123456789000000': No 2G Auth Data DAUC IMSI='123456789000000': No 3G Auth Data -- cgit v1.2.3