├── LICENSE
├── README.md
├── nsa
├── pics
├── banner.png
├── comparision.png
├── comparision.xcf
├── screenshot_init.png
├── screenshot_init.xcf
├── screenshot_someuse.png
└── screenshot_someuse.xcf
└── tests
├── Makefile
├── striptimestamps.py
└── truth
├── -h
├── -v
├── data
├── test_db.add.str.cln
├── test_db.create.str.cln
├── test_db.del.str.cln
└── test_db.mod.str.cln
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MIGRATED TO [_https://codeberg.org/caveman/nsapass_](https://codeberg.org/caveman/nsapass)
2 |
3 | ## WARNING: This repository is an outdated version of [nsapass](https://codeberg.org/caveman/nsapass), and is kept only to link visitors to migrate to the [newer repository](https://codeberg.org/caveman/nsapass).
4 |
5 | ---
6 | ---
7 | ---
8 |
9 | # Synopsis
10 | _nsapass_ is the simplest, most usable and most secure
11 | [CLI](https://en.wikipedia.org/wiki/Command-line_interface) passwords
12 | manager for GNU/Linux. Because all other password managers are either too
13 | complex to be audit-able, lack critical features, or both.
14 |
15 | 
16 | Fig 1. Initial setup.
17 |
18 | 
19 | Fig 2. Some use.
20 |
21 | ## Features
22 |
23 | - **No funny memory bugs:** Thanks implementing _nsapass_
24 | entirely in Python, we easily win this by simply taking advantage of the
25 | decades already spent by Python's many highly-skilled monkeys in fighting
26 | all sorts of funny memory bugs and security issues.
27 |
28 | - **Easy to audit:** _nsapass_ is just a single file, in about `800`
29 | lines of code, including its configurations. This makes it actually
30 | auditable in a practical manner.
31 |
32 | Auditability of a password manager is extremely critical and must be a
33 | fundamental requirement. Compare this to the countless
34 | practically-unauditable C/C++ lines in the likes of
35 | [_keepassxc_](https://github.com/keepassxreboot/keepassxc) (yikes!).
36 | Would you entrust your passwords to them? I would not, henceforth
37 | _nsapass_.
38 |
39 | - **Very powerful:** _nsapass_ is simple, but not stupidly. It allows you
40 | to define your own external commands for encrypting, decrypting or doing
41 | whatever you want with your retrieved passwords/keyfiles/URIs entries, by
42 | simply editing configuration variables `ENCRYPT_COMMAND`,
43 | `DECRYPT_COMMAND`, `DO_COMMANDS` which are inside `nsa` script itself.
44 |
45 | This allows for neat automations. The argument `-c` allows to define a
46 | sequence of actions. E.g. with default `DO_COMMANDS`, `nsa do caveman -c
47 | copypass paste delclip` will 1st load the password into clipboard, paste
48 | it by emulating `Ctrl+V`, and then deleting the password in the
49 | clipboard.
50 |
51 | You can define your own automations, with your own magical external
52 | commands. E.g. you may even skip the clipboard and do it entirely using
53 | keyboard emulation? Your imagination is the limit!
54 |
55 | - **Less password typing:** First you load your passwords database into an
56 | _nsapass_ backend by`nsa start`, and then enjoy using it without password
57 | prompts by commands such a `nsa ls ...`, `nsa add ...`, etc. `nsa -h`
58 | for details.
59 |
60 | This not only enhances the convenience, but is necessary to practically
61 | increase our security, as password databases must be encrypted by
62 | high-entropy passwords that are _rarely_ typed. Minimising moments when
63 | we type such high-entropy password reduces password theft windows (e.g.
64 | look-behind-shoulder).
65 |
66 | - **Smart tags-based search for minimal typing:** All password entries are
67 | stored with tags. You don't have to fully type a tag's name. _nsapass_
68 | has a smart tags lookup system.
69 |
70 | E.g. if you'd like to pick the entry associated with the tags `caveman
71 | protonmail`, you may identify it the boring way by `nsa do caveman protonmail`, or by
72 | just typing `nsa do c p` if it's the only entry with tags that begin with
73 | `c` and `p`. _nsapass_ will intelligently figure out that `c p` must
74 | have been referring to `caveman protonmail`, based on how unique the
75 | match is against tags of other entries in the database..
76 |
77 | - **Common sense:** Your passwords database never touches the disk in plain
78 | text form. If you have disk swap memory, or cybernation, make sure
79 | they're encrypted (or disable them; who needs them these days?).
80 |
81 | ## A comparison
82 |
83 | - [_keepassxc_](https://github.com/keepassxreboot/keepassxc) has loads of
84 | lines of codes that makes it effectively not audit-able, and it's CLI is
85 | terrible. So it loses on both of the auditability and usability
86 | dimensions.
87 | - [_pass_](https://www.passwordstore.org/) has roughly about the same lines
88 | of codes as _nsapass_, so it is sort of auditable, but it:
89 | - Exposes each password entry as a file with a meaningful name (so that
90 | the user remembers it). The file names are obviously in plain text in
91 | the file system. If file names are relevant to passwords inside them,
92 | then it leaks information should the disk be stolen. If the file names
93 | are not relevant, then it becomes not usable as you'll need to remember
94 | odd names unrelated to passwords within them.
95 |
96 | _nsapass_ doesn't leak any information about the
97 | entries, as the whole database is stored in a single encrypted file.
98 | You will be free to choose the most memorable method to tag your entry,
99 | without conerns of leaking it in plain text in the file system.
100 |
101 | - Is limited to `gpg` for file encryption and decryption; a bloated tool.
102 |
103 | _nsapass_ not only allows you to use any file encryption and decryption
104 | tool of your choice, but also any key derivation function.
105 |
106 | - Lots of limitations:
107 | - E.g. _pass_'s password generation isn't able to generate desired
108 | passwords based on target entropy bits (at least not out of the box).
109 |
110 | _nsapass_ does this neatly with the `-b BITS` argument.
111 |
112 | - E.g. _pass_ uses the directory structure offered by the file system
113 | to organise its passwords. This requires too much typing to
114 | identify a given password. E.g. suppose that a password entry is
115 | stored in `path/to/foo/.../baz` and suppose that the entry is already
116 | made unique by `path/.../baz`, you will still need to type
117 | `path/to/foo/../baz` entirely. Why not just type `path baz` and let
118 | it figure out that you meant that? No good reason.
119 |
120 | _nsapass_ uses a smart tagging system that can effectively achieve
121 | that heirarichal partitioning of entries, without needing to type
122 | their names fully. E.g. in the example above, you can retrieve that
123 | entry by not only typing `path baz` (which is already great), but
124 | even by simply typing `p b` if those partial tags make the full tags
125 | unique already.
126 |
127 | - ...
128 |
129 | So, when considering the limited features of _pass_ compared to those of
130 | _nsapass_, _pass_ rather feels very bloated; _nsapass_ does much more
131 | with about the same size of about `800` lines of code.
132 |
133 | # Installation
134 |
135 | 1. **Optional:** Edit file `nsa` to apply your configurations.
136 | 1. Paste the file `nsa` in wherever you'd like it to be. Perhaps somewhere
137 | in `PATH`.
138 |
139 | # Tutorial
140 |
141 | ## Database creation and housekeeping
142 | 1. `nsa create` to create an empty database. This is done only once.
143 | 1. `nsa start` will load the nsapass server.
144 | 1. In a separate terminal, use commands `nsa (ls | add | del | mod | diff |
145 | commit | revert)` to modify the passwords database. Here is an example:
146 | 1. `nsa add -t caveman protonmail -b 256` will
147 | add an automatically generated password worth `256` many Shannon's
148 | entropy bits for my _ProtonMail_ account, using the default
149 | characters space (`printable`), and associates it with the tags
150 | `caveman protonmail` for convenient retrieval in the future.
151 | 1. `nsa diff` will view the total changes made so far to the passwords
152 | database.
153 | 1. If changes are not fine, undo them by `nsa del caveman
154 | protonmail`, or `nsa revert`. The latter will reset the database to
155 | latest committed version.
156 | 1. If changes are are good, then save them by `nsa commit`. Saving them
157 | is necessary to make the changes permanent.
158 | 1. When no longer in need of _nsapass_, execute `nsa stop` to stop the
159 | server.
160 |
161 | ## An example of convenient use
162 |
163 | I've added these shortcuts to my `i3` window manager:
164 | ```
165 | bindsym $mod+i exec nsa do -c delclip
166 | bindsym --release $mod+comma exec nsa do -c copyuser
167 | bindsym --release $mod+period exec nsa do -c copypass
168 | bindsym --release $mod+g exec nsa do -c sleep copypass paste delclip enter
169 | bindsym --release $mod+shift+g exec nsa do -c sleep copyuser paste delclip enter copypass paste delclip enter
170 | ```
171 |
172 | Suppose that I'd like use entries that associate with the tags `EXAMPLE TAGS`.
173 | I perform these in order:
174 |
175 | 1. I start the _nsapass_ server in some terminal by `nsa start`, and let it
176 | run there for as long as I want to access my passwords database.
177 | 1. I execute `nsa do EXAMPLE TAGS` (or just `nsa do E` if partial tags
178 | query `c p` uniquely identifies tags `EXAMPLE TAGS`) in order to load
179 | the entry.
180 | 1. Then I use the username, password or keyfile entries depending on the
181 | way the application works.
182 | - For general applications, I load username and password entries into
183 | the clipboard by `$mod+comma` and `$mod+period`, respectively, then
184 | paste them by `control+v`. I finally delete them by `$mod+i`.
185 | - For CLI applications that have a well defined order of prompts, such
186 | as `git`, I use `$mod+shift+g` to have _nsapass_ automatically
187 | copy-paste-delete username and password entries, successively, using a
188 | single shortcut.
189 | - For CLI applications that only need the password, such as `ssh
190 | user@server`, I use `$mod+g` which only copy-pastes-deletes the
191 | password.
192 |
193 |
194 | It should be also possible to (but I personally don't do it this way):
195 |
196 | - Make it even more convenient, by configuring shortcuts with GUI prompts,
197 | that take advantage of the fact that steps `nsa do EXAMPLE TAGS` and,
198 | say, `nsa do -c copyuser` can be combined in a single step `nsa do
199 | EXAMPLE TAGS -c copyuser`.
200 | - Automate command execution. E.g. command `z` could be defined in such a
201 | way that `nsa do EXAMPLE TAGS -c z ...` would execute `git push` and feed
202 | it with passwords as earlier.
203 |
204 | # Dependencies
205 |
206 | - Python.
207 | - Any key derivation function tool. Default:
208 | [`argon2`](https://github.com/p-h-c/phc-winner-argon2).
209 | - Any file encryption and decryption tool. Default:
210 | [`openssl`](https://www.openssl.org/).
211 | - Any external commands to do whatever you want with your entries.
212 | Default: [`xclip`](https://github.com/astrand/xclip) and
213 | [`xdotool`](http://www.semicomplete.com/projects/xdotool) for clipboard
214 | management and keyboard emulation, respectively.
215 |
216 | # Manual
217 |
218 | ```
219 | usage: nsa [-h] [-v] [-V] [-C] [-i DIR]
220 | {create,chpass,start,stop,do,add,del,mod,ls,diff,commit,revert} ...
221 |
222 | optional arguments:
223 | -h, --help show this help message and exit
224 | -v show information about nsapass
225 | -V show debugging information
226 | -C disable colourful output
227 | -i DIR ipc directory
228 |
229 | commands:
230 | {create,chpass,start,stop,do,add,del,mod,ls,diff,commit,revert}
231 | create create a databases
232 | chpass change databases's password
233 | start starts nsapass
234 | stop stops nsapass and discards any uncommitted changes
235 | do do things (e.g. type passwords)
236 | add add an entry
237 | del delete an entry
238 | mod modify an entry
239 | ls view entries
240 | diff show modifications done so far
241 | commit commit changes to the database
242 | revert revert all uncommitted changed back to original
243 | ```
244 | ```
245 | usage: nsa create [-h] [-d DB] [-s]
246 |
247 | optional arguments:
248 | -h, --help show this help message and exit
249 | -d DB set passwords database path
250 | -s input from stdin
251 | ```
252 | ```
253 | usage: nsa chpass [-h] [-s]
254 |
255 | optional arguments:
256 | -h, --help show this help message and exit
257 | -s input from stdin
258 | ```
259 | ```
260 | usage: nsa start [-h] [-s] [-d DB]
261 |
262 | optional arguments:
263 | -h, --help show this help message and exit
264 | -s input from stdin
265 | -d DB set passwords database path
266 | ```
267 | ```
268 | usage: nsa stop [-h] [-s]
269 |
270 | optional arguments:
271 | -h, --help show this help message and exit
272 | -s input from stdin
273 | ```
274 | ```
275 | usage: nsa do [-h] [-s] [-c COMMANDS [COMMANDS ...]] [QUERY ...]
276 |
277 | positional arguments:
278 | QUERY query tags
279 |
280 | optional arguments:
281 | -h, --help show this help message and exit
282 | -s input from stdin
283 | -c COMMANDS [COMMANDS ...]
284 | perform actions specified in COMMANDS in order from
285 | left to right. COMMANDS are defined in option
286 | DO_COMMANDS, which currently are: copyuser, copypass,
287 | copyuri, delclip, paste, enter, keyfile, sleep
288 | ```
289 | ```
290 | usage: nsa add [-h] [-t TAG [TAG ...]] [-u USERNAME] [-p SET] [-o LETTERS]
291 | [-b BIT] [-l LEN] [-m] [-f PATH] [-r URI] [-n NOTE] [-s] [-z]
292 |
293 | optional arguments:
294 | -h, --help show this help message and exit
295 | -t TAG [TAG ...] new tags
296 | -u USERNAME new username
297 | -p SET pre-defined password letters set name
298 | -o LETTERS raw password letter options
299 | -b BIT generate BIT-entropy password from SET
300 | -l LEN generate LEN-long password from SET
301 | -m user-defined password
302 | -f PATH key/data file in PATH, or STDIN if "-"
303 | -r URI a uniform resource identifier
304 | -n NOTE a note
305 | -s input from stdin
306 | -z show passwords
307 | ```
308 | ```
309 | usage: nsa del [-h] [-s] [-z] [QUERY ...]
310 |
311 | positional arguments:
312 | QUERY query tags
313 |
314 | optional arguments:
315 | -h, --help show this help message and exit
316 | -s input from stdin
317 | -z show passwords
318 | ```
319 | ```
320 | usage: nsa mod [-h] [-t TAG [TAG ...]] [-u USERNAME] [-p SET] [-o LETTERS]
321 | [-b BIT] [-l LEN] [-m] [-f PATH] [-r URI] [-n NOTE] [-s] [-z]
322 | [QUERY ...]
323 |
324 | positional arguments:
325 | QUERY query tags
326 |
327 | optional arguments:
328 | -h, --help show this help message and exit
329 | -t TAG [TAG ...] new tags
330 | -u USERNAME new username
331 | -p SET pre-defined password letters set name
332 | -o LETTERS raw password letter options
333 | -b BIT generate BIT-entropy password from SET
334 | -l LEN generate LEN-long password from SET
335 | -m user-defined password
336 | -f PATH key/data file in PATH, or STDIN if "-"
337 | -r URI a uniform resource identifier
338 | -n NOTE a note
339 | -s input from stdin
340 | -z show passwords
341 | ```
342 | ```
343 | usage: nsa ls [-h] [-s] [-z] [QUERY ...]
344 |
345 | positional arguments:
346 | QUERY query tags
347 |
348 | optional arguments:
349 | -h, --help show this help message and exit
350 | -s input from stdin
351 | -z show passwords
352 | ```
353 | ```
354 | usage: nsa diff [-h] [-s] [-z]
355 |
356 | optional arguments:
357 | -h, --help show this help message and exit
358 | -s input from stdin
359 | -z show passwords
360 | ```
361 | ```
362 | usage: nsa commit [-h] [-s] [-d DB]
363 |
364 | optional arguments:
365 | -h, --help show this help message and exit
366 | -s input from stdin
367 | -d DB set passwords database path
368 | ```
369 | ```
370 | usage: nsa revert [-h] [-s]
371 |
372 | optional arguments:
373 | -h, --help show this help message and exit
374 | -s input from stdin
375 | ```
376 |
377 | # Contact
378 |
379 | - Web: https://codeberg.org/caveman/nsapass
380 |
381 |
--------------------------------------------------------------------------------
/nsa:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | #
3 | # nsapass - the simplest, most usable and most secure passwords manager.
4 | # Copyright (C) 2021 caveman
5 | # https://github.com/al-caveman/nsapass
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 |
20 | import argparse
21 | import os
22 | import sys
23 | import string
24 | import re
25 | import json
26 | import subprocess
27 | import time
28 | import datetime
29 | import math
30 | import secrets
31 | import hashlib
32 | import atexit
33 | import getpass
34 | import base64
35 | import signal
36 | import fcntl
37 |
38 | #######################################
39 | # __ #
40 | # configs below <(o )___ #
41 | # ( ._> / #
42 | #######################################
43 |
44 | # location of ipc's named pipes
45 | IPC_DIR = '/tmp/nsapass'
46 |
47 | # encoding of strings
48 | ENCODING = 'utf-8'
49 |
50 | # location of encrypted passwords db
51 | DATABASE_PATH = os.path.expanduser('~/.local/share/nsapass/db')
52 |
53 | # password character sets
54 | PASSWORD_SETS = {
55 | 'alphanumerics' : string.ascii_letters + string.digits,
56 | 'hexdigits' : string.hexdigits,
57 | }
58 |
59 | # default passwords set
60 | DEFAULT_SET = 'alphanumerics'
61 |
62 | # server timeout in seconds. 0 for no timeout.
63 | TIMEOUT = 30
64 |
65 | # key derivation function command. set it to None to disable it
66 | KDF_COMMAND = {
67 | 'COMMAND' : ['argon2', 'nsapasssalt', '-d', '-t', '20',
68 | '-k', '1048576', '-p', '4', '-r'],
69 | 'STDIN' : '{DATABASE_PASSWORD}',
70 | 'TAKE_STDERR' : False,
71 | 'TAKE_STDOUT' : True,
72 | 'RETURN_VALUES' : {0:None, '*':'argon2 error'}}
73 |
74 | # encrypt command
75 | ENCRYPT_COMMAND = {
76 | 'COMMAND' : ['openssl', 'enc', '-chacha20',
77 | '-out={DATABASE_PATH}', '-e',
78 | '-k={DATABASE_PASSWORD}', '-iter=1'],
79 | 'STDIN' : '{DATABASE_DATA}',
80 | 'TAKE_STDERR' : False,
81 | 'TAKE_STDOUT' : True,
82 | 'RETURN_VALUES' : {0:None, '*':'openssl error'}}
83 |
84 | # decrypt command
85 | DECRYPT_COMMAND = dict(ENCRYPT_COMMAND)
86 | DECRYPT_COMMAND['COMMAND'] = ['openssl', 'enc', '-chacha20',
87 | '-in={DATABASE_PATH}', '-d',
88 | '-k={DATABASE_PASSWORD}', '-iter=1']
89 | DECRYPT_COMMAND['STDIN'] = ''
90 |
91 | # commands used by `nsa do -c COMMANDS`. each command must be a single
92 | # letter. below is an example of defining commands to copy and paste
93 | # various fields from the database.
94 | template_command = {
95 | 'COMMAND' : ['xdotool', '-'],
96 | 'STDIN' : '',
97 | 'TAKE_STDERR' : False,
98 | 'TAKE_STDOUT' : False,
99 | 'RETURN_VALUES' : {0:None, '*':'external command error'}}
100 | DO_COMMANDS = {c:dict(template_command) for c in [
101 | 'uri', 'user', 'pass', 'cli', 'web', 'cat']}
102 | DO_COMMANDS['uri' ]['STDIN'] = "type '{URI}'"
103 | DO_COMMANDS['user']['STDIN'] = "type '{USERNAME}'"
104 | DO_COMMANDS['pass']['STDIN'] = "type '{PASSWORD}'"
105 | DO_COMMANDS['cli' ]['STDIN'] = \
106 | 'type "{USERNAME}"\nkey Return\ntype "{PASSWORD}"\nkey Return'
107 | DO_COMMANDS['web' ]['STDIN'] = \
108 | 'type "{USERNAME}"\nkey Tab\ntype "{PASSWORD}"\nkey Return'
109 | DO_COMMANDS['cat' ]['COMMAND'] = ['cat']
110 | DO_COMMANDS['cat' ]['STDIN' ] = '{DATA}'
111 |
112 | # theme
113 | TIME = '%Y-%m-%d %H:%M'
114 | COLOURS = {'RED' : '\33[0;31m', 'BRED' : '\33[1;31m',
115 | 'BGRED' : '\33[0;41m', 'YELLOW' : '\33[0;33m',
116 | 'BGREEN' : '\33[1;32m', 'BLUE' : '\33[0;34m',
117 | 'GRAY' : '\33[0;90m', 'MAGENTA': '\33[0;35m',
118 | 'RESET' : '\33[0m'}
119 | FORMAT = {
120 | 'DEBUG' : '{MAGENTA}[debug]{RESET} {}\n',
121 | 'INFO' : '{BGREEN}*{RESET} {}\n',
122 | 'WARN' : '{YELLOW}*{RESET} {}\n',
123 | 'ERROR' : '{RED}[error]{RESET} {}\n',
124 | 'PROMPT' : '{BLUE}< {}{RESET} ',
125 | 'PROMPTSTDIN' : '{BLUE}< {} (stdin){RESET}\n',
126 | 'TAGS' : '{BGREEN}{}{RESET}',
127 | 'USERNAME' : '{YELLOW}[{}{YELLOW}]{RESET}',
128 | 'PASSWORD' : '{RED}[{BGRED}{}{RED}]{RESET}',
129 | 'DIGEST' : '{RED}[{:.10}{RED}]{RESET}',
130 | 'TIME' : '{BLUE}{}{RESET}',
131 | 'URI' : '{MAGENTA}[{}{MAGENTA}]{RESET}',
132 | 'NOTE' : '{GRAY}[{}]{RESET}',
133 | 'HIDDEN' : '{GRAY}******{RESET}',
134 | 'NONE' : '',
135 | 'ADD' : '{BGREEN}+ {RESET}',
136 | 'DEL' : '{BRED}- {RESET}',
137 | 'LIST' : '{DIFF}{TIME} {TAGS} {USERNAME} {PASSWORD} '
138 | '{DIGEST} {URI} {NOTE}\n'}
139 |
140 | #########################################
141 | # #
142 | # WARNING: DEVS ONLY #
143 | # #
144 | #########################################
145 |
146 | VERSION = '10'
147 | YEAR = '2022'
148 |
149 | CMD_CREATE = 'create'
150 | CMD_CHPASS = 'chpass'
151 | CMD_START = 'start'
152 | CMD_STOP = 'stop'
153 | CMD_DO = 'do'
154 | CMD_ADD = 'add'
155 | CMD_DEL = 'del'
156 | CMD_MOD = 'mod'
157 | CMD_LS = 'ls'
158 | CMD_DIFF = 'diff'
159 | CMD_COMMIT = 'commit'
160 | CMD_DROP = 'drop'
161 |
162 | ARG_INFO = 'v'
163 | ARG_STDIN_INPUT = 's'
164 | ARG_DB_PATH = 'd'
165 | ARG_QUERY = 'query'
166 | ARG_SHOWPASS = 'z'
167 | ARG_TAGS = 't'
168 | ARG_USERNAME = 'u'
169 | ARG_PASS_SET = 'p'
170 | ARG_PASS_OPT = 'o'
171 | ARG_PASS_BIT = 'b'
172 | ARG_PASS_LEN = 'l'
173 | ARG_PASS_MAN = 'm'
174 | ARG_PASS_KEY = 'f'
175 | ARG_URI = 'r'
176 | ARG_NOTE = 'n'
177 | ARG_COMMANDS = 'c'
178 | ARG_DEBUG = 'V'
179 | ARG_NOCOLOUR = 'C'
180 | ARG_IPC_DIR = 'i'
181 |
182 | KEY_DB_USERNAME = 'username'
183 | KEY_DB_PASS = 'password'
184 | KEY_DB_DATAB64 = 'datab64s'
185 | KEY_DB_DIGEST = 'digest'
186 | KEY_DB_URI = 'uri'
187 | KEY_DB_NOTE = 'note'
188 | KEY_DB_TIME = 'timestamp'
189 |
190 | KEY_IPC_CMD = 'command'
191 | KEY_IPC_ARGS = 'arguments'
192 | KEY_IPC_PATH = 'path'
193 | KEY_IPC_REPLY_TYPE = 'type'
194 | KEY_IPC_REPLY_DATA = 'data'
195 |
196 | VAL_IPC_REPLY_TYPE_OUT = 'out'
197 | VAL_IPC_REPLY_TYPE_DEBUG = 'debug'
198 | VAL_IPC_REPLY_TYPE_INFO = 'info'
199 | VAL_IPC_REPLY_TYPE_WARN = 'warn'
200 | VAL_IPC_REPLY_TYPE_ERR = 'err'
201 | VAL_IPC_REPLY_TYPE_CMD = 'command'
202 | VAL_IPC_REPLY_TYPE_ENTRY = 'entry'
203 |
204 | FRMT2KEY = {'USERNAME': KEY_DB_USERNAME, 'PASSWORD': KEY_DB_PASS,
205 | 'DATA' : KEY_DB_DATAB64, 'DIGEST' : KEY_DB_DIGEST,
206 | 'URI' : KEY_DB_URI, 'NOTE' : KEY_DB_NOTE,
207 | 'TIME' : KEY_DB_TIME}
208 | KEY2FRMT = {value:key for key, value in FRMT2KEY.items()}
209 |
210 | enable_debug = True
211 |
212 | def frmtstr(s, frmt, show=True):
213 | """formats a value"""
214 | if not s: s = FORMAT['NONE'].format(s, **COLOURS)
215 | elif not show: s = FORMAT['HIDDEN'].format(s, **COLOURS)
216 | return FORMAT[frmt].format(s, **COLOURS)
217 |
218 | def frmtentry(tags, entry, showpass, diff=None):
219 | """formats a database entry"""
220 | local_time = datetime.datetime.fromtimestamp(
221 | entry[KEY_DB_TIME], datetime.timezone.utc).astimezone()
222 | values = {'DIFF': frmtstr(diff, diff) if diff else '',
223 | 'TAGS': frmtstr(tags, 'TAGS')}
224 | for frmt, key in FRMT2KEY.items():
225 | if key == KEY_DB_DATAB64: continue
226 | show = showpass if key == KEY_DB_PASS else True
227 | s = local_time.strftime(TIME) if key == KEY_DB_TIME else entry[key]
228 | values[frmt] = frmtstr(s, frmt, show)
229 | return FORMAT['LIST'].format(**values)
230 |
231 | def log(msg, frmt):
232 | """log various message types to terminal"""
233 | sys.stderr.write(frmt.format(msg, **COLOURS))
234 | sys.stderr.flush()
235 | def debug(msg):
236 | if enable_debug: log(msg, FORMAT['DEBUG'])
237 | def info(msg): log(msg, FORMAT['INFO'])
238 | def warn(msg): log(msg, FORMAT['WARN'])
239 | def err(msg): log(msg, FORMAT['ERROR'])
240 |
241 | def cleanwords(words):
242 | """guarantee that words are single-space-separated words or None"""
243 | if not words: return None
244 | if type(words) is list: words = ' '.join(words)
245 | return ' '.join(words.split())
246 |
247 | def find(db, query):
248 | """finds tags"""
249 | tags_list = list(db)
250 | tags_list.sort()
251 | query = cleanwords(query)
252 | if not query: return tags_list
253 | if query in tags_list: return [query]
254 | words_escaped = [re.escape(word) for word in query.split()]
255 | query_re = r'.*? .*?'.join(words_escaped)
256 | return [tags for tags in tags_list if re.search(query_re, tags)]
257 |
258 | def askpass(name, confirm=False, stdin=False):
259 | """prompts users for password"""
260 | f = input if stdin else getpass.getpass
261 | prompt = 'PROMPTSTDIN' if stdin else 'PROMPT'
262 | pass1 = f(frmtstr(f'{name} password:', prompt))
263 | pass2 = pass1
264 | if confirm:
265 | pass1 = f(frmtstr(f'confirm password:', prompt))
266 | if pass1 == pass2:
267 | return pass1
268 | err('passwords mismatched')
269 | sys.exit(1)
270 |
271 | def genpass(bits, length, letters):
272 | """dynamically generates passwords"""
273 | if length and length < 1:
274 | err(f'password length "{length}" is not positive')
275 | sys.exit(1)
276 | if bits:
277 | if bits < 1:
278 | err(f'password bits count "{bits}" is not positive')
279 | sys.exit(1)
280 | bits_to_length = math.ceil(bits / math.log2(len(letters)))
281 | if not length or bits_to_length > length: length = bits_to_length
282 | password = ''.join(secrets.choice(letters) for i in range(0, length))
283 | return password
284 |
285 | def bin2str(data):
286 | """encode binary data into a string"""
287 | return base64.b64encode(data).decode()
288 |
289 | def str2bin(s):
290 | """decode binary data from a string"""
291 | return base64.b64decode(s) if s else None
292 |
293 | def readbin(path):
294 | """read binary data"""
295 | debug(f'reading binary data from "{path}"...')
296 | try:
297 | with open(path, 'rb') as f:
298 | debug(f'shared-locking "{path}"...')
299 | fcntl.lockf(f, fcntl.LOCK_SH)
300 | data = f.read()
301 | except FileNotFoundError:
302 | if path != '-':
303 | err(f'file "{path}" not found')
304 | sys.exit(1)
305 | data = sys.stdin.buffer.read()
306 | return data
307 |
308 | def run(command, values):
309 | """run an external command"""
310 | debug(f'running command "{command}"')
311 | debug(f'command values "{values}"')
312 | command_formatted = [a.format(**values) for a in command['COMMAND']]
313 | try:
314 | p = subprocess.Popen(command_formatted,
315 | stdin = subprocess.PIPE if command['STDIN'] else None,
316 | stdout = subprocess.PIPE if command['TAKE_STDOUT'] else None,
317 | stderr = subprocess.PIPE if command['TAKE_STDERR'] else None)
318 | except FileNotFoundError:
319 | err(f'command `{command_formatted[0]}` not found')
320 | sys.exit(1)
321 | except IndexError:
322 | err(f'command is not defined')
323 | sys.exit(1)
324 | except KeyError as e:
325 | err(f'command `{command_formatted[0]}` lacks key {e}')
326 | sys.exit(1)
327 | stdin = b''
328 | for before, field, _, _ in string.Formatter().parse(command['STDIN']):
329 | stdin += before.encode(ENCODING)
330 | if field is None: continue
331 | if field not in values:
332 | err(f'unknown command format field "{field}"')
333 | sys.exit(1)
334 | value = values[field]
335 | if field == 'DATA': stdin += str2bin(value)
336 | elif type(values[field]) is str: stdin += value.encode(ENCODING)
337 | elif type(values[field]) is bytes: stdin += value
338 | else:
339 | err(f'unknown command field data type "{type(part)}"')
340 | sys.exit(1)
341 | p_stdout, _ = p.communicate(input=stdin)
342 | debug(f'return code "{p.returncode}"')
343 | r = p.returncode if p.returncode in command['RETURN_VALUES'] else '*'
344 | return p_stdout, command['RETURN_VALUES'][r]
345 |
346 | def kdf(password):
347 | """key derivation function"""
348 | if KDF_COMMAND:
349 | values = {'DATABASE_PASSWORD':password}
350 | password, kdf_err = run(KDF_COMMAND, values)
351 | if kdf_err:
352 | err(kdf_err)
353 | sys.exit(1)
354 | password = password.decode(ENCODING).rstrip('\n')
355 | debug(f'derived key "{password}"')
356 | return password
357 |
358 | def load_json(path):
359 | """loads a json file"""
360 | debug(f'loading json file "{path}"...')
361 | if not os.path.exists(path):
362 | err(f'could not find file "{path}"')
363 | sys.exit(1)
364 | with open(path, 'r') as f:
365 | try:
366 | return json.load(f)
367 | except json.JSONDecodeError:
368 | err(f'failed to decode json file "{path}"')
369 | sys.exit(1)
370 |
371 | def load_db(path, stdin, password=None):
372 | """load database"""
373 | debug(f'loading "{path}"...')
374 | if not os.path.exists(path):
375 | err(f'database "{path}" not found. did you create it?')
376 | sys.exit(1)
377 | if not password:
378 | password = kdf(askpass('database', stdin=stdin))
379 | values = {'DATABASE_PATH' : path, 'DATABASE_PASSWORD' : password}
380 | data, err_msg = run(DECRYPT_COMMAND, values)
381 | if err_msg:
382 | err(err_msg)
383 | sys.exit(1)
384 | try:
385 | db = json.loads(data)
386 | return db, password
387 | except (json.JSONDecodeError, UnicodeDecodeError):
388 | err(f'error decoding database "{path}". bad password?')
389 | sys.exit(1)
390 |
391 | def save_db(db, path, password):
392 | """save database"""
393 | debug(f'saving to "{path}"...')
394 | data = json.dumps(db, indent=4)
395 | values = {'DATABASE_PATH' : path,
396 | 'DATABASE_PASSWORD' : password,
397 | 'DATABASE_DATA' : data}
398 | _, err_msg = run(ENCRYPT_COMMAND, values)
399 | if err_msg:
400 | err(err_msg)
401 | sys.exit(1)
402 |
403 | def ipc_init(path):
404 | """prepares stuff needed for ipc"""
405 | dirname = os.path.dirname(path)
406 | if len(dirname): os.makedirs(dirname, exist_ok=True)
407 | try:
408 | debug(f'creating ipc file "{path}"...')
409 | os.mkfifo(path, mode=400)
410 | except NotImplementedError:
411 | err("your operating system doesn't support named pipes")
412 | sys.exit(1)
413 | except FileExistsError:
414 | err(f'file "{path}" exists. delete it if not used')
415 | sys.exit(1)
416 | atexit.register(os.unlink, path)
417 | atexit.register(debug, f'deleting named pipe "{path}"...')
418 |
419 | def ipc_uninit(path):
420 | """uninitialises ipc"""
421 | debug(f'deleting ipc file "{path}"...')
422 | atexit.unregister(os.unlink)
423 | os.unlink(path)
424 |
425 | def ipc_request(path, command, rargs, stdin=False):
426 | """define ipc request messages"""
427 | return {KEY_IPC_CMD:command, KEY_IPC_ARGS:rargs, KEY_IPC_PATH:path}
428 |
429 | def ipc_reply(reply_type, data):
430 | """define ipc reply messages"""
431 | return [{KEY_IPC_REPLY_TYPE : reply_type, KEY_IPC_REPLY_DATA : data}]
432 | def ipc_out(message) : return ipc_reply(VAL_IPC_REPLY_TYPE_OUT, message)
433 | def ipc_debug(message): return ipc_reply(VAL_IPC_REPLY_TYPE_DEBUG, message)
434 | def ipc_info(message) : return ipc_reply(VAL_IPC_REPLY_TYPE_INFO, message)
435 | def ipc_warn(message) : return ipc_reply(VAL_IPC_REPLY_TYPE_WARN, message)
436 | def ipc_err(message) : return ipc_reply(VAL_IPC_REPLY_TYPE_ERR, message)
437 | def ipc_command(values): return ipc_reply(VAL_IPC_REPLY_TYPE_CMD, values)
438 | def ipc_entry(db, tags, showpass, diff=None):
439 | entry = dict(db[tags])
440 | if entry[KEY_DB_PASS] and not showpass: entry[KEY_DB_PASS] = '****'
441 | del entry[KEY_DB_DATAB64]
442 | return ipc_reply(VAL_IPC_REPLY_TYPE_ENTRY, (tags, entry, diff))
443 |
444 | def ipc_do(db, last_query, rargs):
445 | """implements `do` sub-command"""
446 | args, db_keys_list = rargs
447 | query = args[ARG_QUERY] if args[ARG_QUERY] else last_query
448 | tags_list = find(db, query)
449 | if not tags_list: return ipc_err('nothing matched')
450 | if len(tags_list) > 1: return ipc_err('too many matched')
451 | tags = tags_list[0]
452 | info(f'do {FORMAT["TAGS"].format(tags, **COLOURS)}')
453 | replies = []
454 | for keys in db_keys_list:
455 | replies += ipc_command({k:db[tags][k] for k in keys})
456 | return replies
457 |
458 | def ipc_add(db, rargs):
459 | """implements `add` sub-command"""
460 | args, password, datab64s, digest = rargs
461 | tags, username, uri, note, showpass = (args[k] for k in
462 | (ARG_TAGS, ARG_USERNAME, ARG_URI, ARG_NOTE, ARG_SHOWPASS))
463 | tags = cleanwords(tags)
464 | if tags in db: return ipc_err(f'tags "{tags}" already exists')
465 | info(f'add {FORMAT["TAGS"].format(tags, **COLOURS)}')
466 | db[tags] = {KEY_DB_USERNAME : username,
467 | KEY_DB_PASS : password,
468 | KEY_DB_DATAB64 : datab64s,
469 | KEY_DB_DIGEST : digest,
470 | KEY_DB_URI : uri,
471 | KEY_DB_NOTE : note,
472 | KEY_DB_TIME : time.time()}
473 | return ipc_entry(db, tags, showpass, 'ADD')
474 |
475 | def ipc_del(db, rargs):
476 | """implements `del` sub-command"""
477 | query, showpass = rargs[ARG_QUERY], rargs[ARG_SHOWPASS]
478 | tags_list = find(db, query)
479 | if not tags_list:
480 | return ipc_err('nothing matched')
481 | replies = []
482 | for tags in tags_list:
483 | info(f'del {FORMAT["TAGS"].format(tags, **COLOURS)}')
484 | replies += ipc_entry(db, tags, showpass, 'DEL')
485 | del db[tags]
486 | return replies
487 |
488 | def ipc_mod(db, rargs):
489 | """implements `mod` sub-command"""
490 | args, password, datab64s, digest = rargs
491 | query, tags, username, uri, note, showpass = (args[k] for k in
492 | (ARG_QUERY, ARG_TAGS, ARG_USERNAME, ARG_URI, ARG_NOTE,
493 | ARG_SHOWPASS))
494 | tags = cleanwords(tags)
495 | tags_list = find(db, query)
496 | if not tags_list:
497 | return ipc_err('nothing matched')
498 | replies = []
499 | for cur_tags in tags_list:
500 | info(f'mod {FORMAT["TAGS"].format(cur_tags, **COLOURS)}')
501 | entry = db[cur_tags]
502 | replies += ipc_entry(db, cur_tags, showpass, 'DEL')
503 | entry[KEY_DB_TIME] = time.time()
504 | if username: entry[KEY_DB_USERNAME] = username
505 | if password: entry[KEY_DB_PASS] = password
506 | if datab64s:
507 | entry[KEY_DB_DATAB64] = datab64s
508 | entry[KEY_DB_DIGEST] = digest
509 | if uri: entry[KEY_DB_URI] = uri
510 | if note: entry[KEY_DB_NOTE] = note
511 | if tags and tags != cur_tags:
512 | db[tags] = db[cur_tags]
513 | del db[cur_tags]
514 | cur_tags = tags
515 | replies += ipc_entry(db, cur_tags, showpass, 'ADD')
516 | return replies
517 |
518 | def ipc_ls(db, rargs):
519 | """implements `ls` sub-command"""
520 | query, showpass = rargs[ARG_QUERY], rargs[ARG_SHOWPASS]
521 | formatted_query = FORMAT['TAGS'].format(' '.join(query), **COLOURS)
522 | info(f'ls {formatted_query}')
523 | tags_list = find(db, query)
524 | if not tags_list: return ipc_err('nothing matched')
525 | replies = []
526 | for tags in tags_list:
527 | replies += ipc_entry(db, tags, showpass)
528 | return replies
529 |
530 | def ipc_diff(db_tmp, path_orig, password, password_orig, rargs):
531 | """implements `diff` sub-command"""
532 | info('diff')
533 | showpass = rargs[ARG_SHOWPASS]
534 | db_orig, _ = load_db(path_orig, False, password_orig)
535 | replies = []
536 | for tags in db.keys() - db_orig.keys():
537 | replies += ipc_entry(db, tags, showpass, 'ADD')
538 | for tags in db_orig.keys() - db.keys():
539 | replies += ipc_entry(db_orig, tags, showpass, 'DEL')
540 | for tags in db.keys() & db_orig.keys():
541 | if [k for k in db[tags] if db[tags][k] != db_orig[tags][k]]:
542 | replies += ipc_entry(db_orig, tags, showpass, 'DEL')
543 | replies += ipc_entry(db, tags, showpass, 'ADD')
544 | if password != password_orig:
545 | passfrmt = frmtstr(password, 'PASSWORD', showpass)
546 | replies += ipc_warn(f'updated database password to {passfrmt}')
547 | return replies
548 |
549 | def handler_sigint(signum, frame):
550 | """pretty interrupt handling"""
551 | info('received interrupt. exiting...')
552 | sys.exit(0)
553 |
554 | def handler_sigalrm(signum, frame):
555 | """suspend server upon timeout"""
556 | info('server timed out. suspending...')
557 | ipc_uninit(ipc_server_path)
558 | getpass.getpass(frmtstr('hit enter to resume the server', 'PROMPT'))
559 | ipc_init(ipc_server_path)
560 | info('server resumed')
561 | signal.alarm(TIMEOUT)
562 |
563 | # parse arguments
564 | cmdname = os.path.basename(sys.argv[0])
565 | allcmds = [
566 | [CMD_CREATE, [ARG_DB_PATH, ARG_STDIN_INPUT],
567 | {'help':"create a databases"}],
568 | [CMD_CHPASS, [ARG_STDIN_INPUT],
569 | {'help':"change databases's password"}],
570 | [CMD_START, [ARG_STDIN_INPUT, ARG_DB_PATH],
571 | {'help':'starts nsapass'}],
572 | [CMD_STOP, [ARG_STDIN_INPUT],
573 | {'help':'stops nsapass and discards any uncommitted changes'}],
574 | [CMD_DO , [ARG_QUERY, ARG_STDIN_INPUT, ARG_COMMANDS],
575 | {'help':'do things (e.g. type passwords)'}],
576 | [CMD_ADD, [ARG_TAGS, ARG_USERNAME, ARG_PASS_SET, ARG_PASS_OPT,
577 | ARG_PASS_BIT, ARG_PASS_LEN, ARG_PASS_MAN, ARG_PASS_KEY, ARG_URI,
578 | ARG_NOTE, ARG_STDIN_INPUT, ARG_SHOWPASS], {'help':'add an entry'}],
579 | [CMD_DEL, [ARG_QUERY, ARG_STDIN_INPUT, ARG_SHOWPASS],
580 | {'help':'delete an entry'}],
581 | [CMD_MOD, [ARG_QUERY, ARG_TAGS, ARG_USERNAME, ARG_PASS_SET,
582 | ARG_PASS_OPT, ARG_PASS_BIT, ARG_PASS_LEN, ARG_PASS_MAN,
583 | ARG_PASS_KEY, ARG_URI, ARG_NOTE, ARG_STDIN_INPUT, ARG_SHOWPASS],
584 | {'help':'modify an entry'}],
585 | [CMD_LS, [ARG_QUERY, ARG_STDIN_INPUT, ARG_SHOWPASS],
586 | {'help':'view entries'}],
587 | [CMD_DIFF, [ARG_STDIN_INPUT, ARG_SHOWPASS],
588 | {'help':'show modifications done so far'}],
589 | [CMD_COMMIT, [ARG_STDIN_INPUT],
590 | {'help':'commit changes to the database'}],
591 | [CMD_DROP, [ARG_STDIN_INPUT],
592 | {'help':'discard all uncommitted changed'}],
593 | ]
594 | allargs = {
595 | ARG_QUERY : {'metavar':'QUERY', 'type':str, 'nargs':'*',
596 | 'help':'query tags'},
597 | ARG_TAGS : {'metavar':'TAG', 'type':str, 'nargs':'+',
598 | 'help':"new tags"},
599 | ARG_USERNAME: {'metavar':'USERNAME', 'type':str,
600 | 'help':'new username'},
601 | ARG_PASS_SET: {'metavar':'SET', 'type':str, 'default':DEFAULT_SET,
602 | 'help':'pre-defined password letters set name'},
603 | ARG_PASS_OPT: {'metavar':'LETTERS', 'type':str,
604 | 'help':'raw password letter options'},
605 | ARG_PASS_BIT: {'metavar':'BIT', 'type':int,
606 | 'help':'generate BIT-entropy password from SET'},
607 | ARG_PASS_LEN: {'metavar':'LEN', 'type':int,
608 | 'help':'generate LEN-long password from SET'},
609 | ARG_PASS_MAN: {'action':'store_true', 'help':'user-defined password'},
610 | ARG_PASS_KEY: {'metavar':'PATH', 'type':str,
611 | 'help':'data file in PATH, or STDIN if "-"'},
612 | ARG_URI : {'metavar':'URI', 'type':str,
613 | 'help':"a uniform resource identifier"},
614 | ARG_NOTE : {'metavar':'NOTE', 'type':str, 'help':"a note"},
615 | ARG_COMMANDS: {'metavar':'COMMANDS', 'type':str, 'nargs':'+',
616 | 'default':[], 'help': f"""perform actions specified in
617 | COMMANDS in order from left to right. COMMANDS are
618 | defined in option DO_COMMANDS, which currently are:
619 | {', '.join(list(DO_COMMANDS))}"""},
620 | ARG_SHOWPASS : {'action':'store_true', 'help':'show passwords'},
621 | ARG_STDIN_INPUT : {'action':'store_true', 'help':'input from stdin'},
622 | ARG_DB_PATH : {'metavar':'DB', 'type':str,
623 | 'help':'set database path'}}
624 | parser = argparse.ArgumentParser()
625 | parser.add_argument('-' + ARG_INFO, action='store_true',
626 | help='show information about nsapass')
627 | parser.add_argument('-' + ARG_DEBUG, action='store_true',
628 | help='show debugging information')
629 | parser.add_argument('-' + ARG_NOCOLOUR, action='store_true',
630 | help='disable colourful output')
631 | parser.add_argument('-' + ARG_IPC_DIR, metavar='DIR', type=str,
632 | help='ipc directory')
633 | subparsers = parser.add_subparsers(title='commands', dest='command')
634 | for cmd_name, cmd_args, cmd_named in allcmds :
635 | subp = subparsers.add_parser(cmd_name, **cmd_named)
636 | for arg in cmd_args:
637 | arg_prefix = '-' if len(arg) == 1 else ''
638 | subp.add_argument(arg_prefix + arg, **allargs[arg])
639 | args = parser.parse_args()
640 |
641 | # some usability stuff
642 | if args.v:
643 | sys.stdout.write(
644 | f"nsapass v{VERSION} copyright (C) {YEAR} caveman\n"
645 | "https://github.com/Al-Caveman/nsapass\n\n"
646 | "this program comes with ABSOLUTELY NO WARRANTY; for details\n"
647 | "read https://github.com/Al-Caveman/nsapass/blob/master/LICENSE\n")
648 | sys.exit()
649 | if args.command is None:
650 | parser.print_help()
651 | sys.exit()
652 | enable_debug = args.V
653 | if args.C:
654 | for c in COLOURS: COLOURS[c] = ''
655 | if args.i: IPC_DIR = args.i
656 | ipc_server_path = f'{IPC_DIR}/server'
657 |
658 | # register signal handlers
659 | signal.signal(signal.SIGINT, handler_sigint)
660 | signal.signal(signal.SIGTERM, handler_sigint)
661 | signal.signal(signal.SIGALRM, handler_sigalrm)
662 |
663 | # starts the server loop
664 | if args.command == CMD_START:
665 | ipc_init(ipc_server_path)
666 | path = args.d if args.d else DATABASE_PATH
667 | path_tmp = path + '.temp'
668 | if os.path.exists(path_tmp):
669 | warn(f'found uncommitted changes from a previous session in "{path_tmp}"')
670 | warn(f're-loading the uncommitted changes...')
671 | db, password = load_db(path_tmp, args.s)
672 | warn(f'execute:')
673 | warn(f' `{cmdname} {CMD_DIFF}` to see the uncommitted changes')
674 | warn(f' `{cmdname} {CMD_COMMIT}` to commit them')
675 | warn(f' `{cmdname} {CMD_DROP}` to discard them')
676 | else:
677 | db, password = load_db(path, args.s)
678 | password_orig = password
679 | last_do_query = None
680 | info('waiting for ipc commands...')
681 | while True: # runs indefinitely until stopped by SIGINT or `CMD_STOP`
682 | signal.alarm(TIMEOUT)
683 | request = load_json(ipc_server_path)
684 | rcmd = request[KEY_IPC_CMD]
685 | rargs = request[KEY_IPC_ARGS]
686 | client_ipc_path = request[KEY_IPC_PATH]
687 | debug(f'got ipc request {rcmd} {rargs}')
688 |
689 | # implement nsapass's sub-commands
690 | replies = []
691 | if rcmd == CMD_CHPASS:
692 | info('updating database password...')
693 | password = kdf(rargs)
694 | save_db(db, path_tmp, password)
695 | replies += ipc_info(
696 | 'database password change staged')
697 | elif rcmd == CMD_DO :
698 | if rargs[0][ARG_QUERY]: last_do_query = rargs[0][ARG_QUERY]
699 | replies += ipc_do(db, last_do_query, rargs)
700 | elif rcmd == CMD_ADD:
701 | replies += ipc_add(db, rargs)
702 | save_db(db, path_tmp, password)
703 | elif rcmd == CMD_DEL:
704 | replies += ipc_del(db, rargs)
705 | save_db(db, path_tmp, password)
706 | elif rcmd == CMD_MOD:
707 | replies += ipc_mod(db, rargs)
708 | save_db(db, path_tmp, password)
709 | elif rcmd == CMD_LS :
710 | replies += ipc_ls(db, rargs)
711 | elif rcmd == CMD_DIFF:
712 | replies += ipc_diff(db, path, password, password_orig, rargs)
713 | elif rcmd == CMD_COMMIT:
714 | if not os.path.exists(path_tmp):
715 | replies += ipc_warn('no changes to commit')
716 | else:
717 | debug(f'moving "{path_tmp}" to "{path}"...')
718 | os.rename(path_tmp, path)
719 | replies += ipc_info(f'changes committed to "{path}"')
720 | elif rcmd == CMD_DROP:
721 | if not os.path.exists(path_tmp):
722 | replies += ipc_warn('no changes to drop')
723 | else:
724 | db, _ = load_db(path, False, password_orig)
725 | debug(f'deleting "{path_tmp}"...')
726 | os.unlink(path_tmp)
727 | replies += ipc_info(f'changes dropped')
728 |
729 | # write replies back to client
730 | debug(f'writing to "{client_ipc_path}" ipc reply {replies}...')
731 | try:
732 | with open(client_ipc_path, 'w') as f:
733 | debug(f'exclusive-locking "{client_ipc_path}"...')
734 | fcntl.lockf(f, fcntl.LOCK_EX)
735 | json.dump(replies, f)
736 | except BrokenPipeError:
737 | err("client's pipe is broken")
738 |
739 | # stop nsapass
740 | if rcmd == CMD_STOP:
741 | info(f'stop')
742 | sys.exit()
743 |
744 | # create nsapass passwords database
745 | if args.command == CMD_CREATE:
746 | path = args.d if args.d else DATABASE_PATH
747 | if os.path.exists(path):
748 | err(f'database "{path}" already exists')
749 | sys.exit(1)
750 | password = kdf(askpass('new database', confirm=True, stdin=args.s))
751 | save_db({}, path, password)
752 | info(f'database created in "{path}"')
753 | sys.exit(0)
754 |
755 | # define values needed for various commands
756 | if args.command == CMD_CHPASS:
757 | new_db_pass = askpass('new database', confirm=True, stdin=args.s)
758 | elif args.command in {CMD_ADD, CMD_MOD}:
759 | password, datab64s, digest = None, None, None
760 | if args.p not in PASSWORD_SETS:
761 | err(f'set "{args.p}" is not among: {", ".join(PASSWORD_SETS)}')
762 | sys.exit(1)
763 | letters = args.o if args.o else PASSWORD_SETS[args.p]
764 | if args.b or args.l: password = genpass(args.b, args.l, letters)
765 | if args.m: password = askpass('new entry', confirm=True, stdin=args.s)
766 | if args.f:
767 | data = readbin(args.f)
768 | datab64s = bin2str(data)
769 | digest = hashlib.sha224(data).hexdigest()
770 | elif args.command == CMD_DO:
771 | db_keys_list = []
772 | for command in args.c:
773 | frmt = ''.join(DO_COMMANDS[command]['COMMAND'])
774 | frmt += DO_COMMANDS[command]['STDIN']
775 | keys = []
776 | for _, field, _, _ in string.Formatter().parse(frmt):
777 | if field is None: continue
778 | if field not in FRMT2KEY:
779 | err(f'unknown do format field "{field}"')
780 | sys.exit(1)
781 | keys.append(FRMT2KEY[field])
782 | db_keys_list.append(keys)
783 |
784 | # terminate useless cases early on
785 | if args.command == CMD_ADD and not args.t:
786 | err('tags are required')
787 | sys.exit(1)
788 | if args.command == CMD_DO and args.c:
789 | unknown_commands = list(set(args.c) - DO_COMMANDS.keys())
790 | if unknown_commands:
791 | err(f'unknown do commands: {", ".join(unknown_commands)}')
792 | sys.exit(1)
793 |
794 | # prepare things to talk to server
795 | ipc_client_path = f'{IPC_DIR}/client.{os.getpid()}'
796 | ipc_init(ipc_client_path)
797 | if args.command == CMD_CHPASS: rargs = new_db_pass
798 | elif args.command in {CMD_ADD, CMD_MOD}:
799 | rargs = [vars(args), password, datab64s, digest]
800 | elif args.command == CMD_DO: rargs = [vars(args), db_keys_list]
801 | else: rargs = vars(args)
802 | request = ipc_request(ipc_client_path, args.command, rargs)
803 |
804 | # send ipc request
805 | debug(f'sending ipc request: {request}')
806 | try:
807 | fd = os.open(ipc_server_path, os.O_WRONLY | os.O_NONBLOCK)
808 | fcntl.lockf(fd, fcntl.LOCK_EX)
809 | os.write(fd, json.dumps(request).encode(ENCODING))
810 | os.close(fd)
811 | except OSError:
812 | err(f'"{ipc_server_path}" not found. nsapass stopped or suspended?')
813 | sys.exit(1)
814 | except BrokenPipeError:
815 | err("server's pipe is broken")
816 | sys.exit(1)
817 |
818 | # processing server's ipc replies
819 | debug(f'waiting for ipc replies...')
820 | replies = load_json(ipc_client_path)
821 | debug(f'got replies: {replies}')
822 | command_i = 0
823 | for reply in replies:
824 | reply_type = reply[KEY_IPC_REPLY_TYPE]
825 | reply_data = reply[KEY_IPC_REPLY_DATA]
826 | if reply_type == VAL_IPC_REPLY_TYPE_CMD:
827 | command = args.c[command_i]
828 | command_i += 1
829 | values = {KEY2FRMT[key]:value for key, value in reply_data.items()}
830 | stdout, err_msg = run(DO_COMMANDS[command], values)
831 | if stdout: sys.stdout.write(stdout)
832 | if err_msg:
833 | err(err_msg)
834 | sys.exit(1)
835 | continue
836 | fout = sys.stdout.write
837 | if reply_type == VAL_IPC_REPLY_TYPE_DEBUG : fout = debug
838 | elif reply_type == VAL_IPC_REPLY_TYPE_INFO : fout = info
839 | elif reply_type == VAL_IPC_REPLY_TYPE_WARN : fout = warn
840 | elif reply_type == VAL_IPC_REPLY_TYPE_ERR : fout = err
841 | elif reply_type == VAL_IPC_REPLY_TYPE_ENTRY:
842 | tags, entry, diff = reply_data
843 | reply_data = frmtentry(tags, entry, args.z, diff)
844 | fout(reply_data)
845 |
--------------------------------------------------------------------------------
/pics/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/al-caveman/nsapass/982d99be4a66cc50e9f6856e9b4fec89f227a8b2/pics/banner.png
--------------------------------------------------------------------------------
/pics/comparision.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/al-caveman/nsapass/982d99be4a66cc50e9f6856e9b4fec89f227a8b2/pics/comparision.png
--------------------------------------------------------------------------------
/pics/comparision.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/al-caveman/nsapass/982d99be4a66cc50e9f6856e9b4fec89f227a8b2/pics/comparision.xcf
--------------------------------------------------------------------------------
/pics/screenshot_init.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/al-caveman/nsapass/982d99be4a66cc50e9f6856e9b4fec89f227a8b2/pics/screenshot_init.png
--------------------------------------------------------------------------------
/pics/screenshot_init.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/al-caveman/nsapass/982d99be4a66cc50e9f6856e9b4fec89f227a8b2/pics/screenshot_init.xcf
--------------------------------------------------------------------------------
/pics/screenshot_someuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/al-caveman/nsapass/982d99be4a66cc50e9f6856e9b4fec89f227a8b2/pics/screenshot_someuse.png
--------------------------------------------------------------------------------
/pics/screenshot_someuse.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/al-caveman/nsapass/982d99be4a66cc50e9f6856e9b4fec89f227a8b2/pics/screenshot_someuse.xcf
--------------------------------------------------------------------------------
/tests/Makefile:
--------------------------------------------------------------------------------
1 | IPC=./ipc_dir
2 | TRU=./truth
3 | CUR=./current
4 | DB=test_db
5 |
6 | NSA =../nsa -i $(IPC)
7 | NSA_CREATE=$(NSA) create -s -d $(CUR)/$(DB)
8 | NSA_START =$(NSA) start -s -d $(CUR)/$(DB) &
9 | NSA_STOP =$(NSA) stop
10 | NSA_CHPASS=$(NSA) chpass -s
11 | NSA_ADD =$(NSA) add -s
12 | NSA_DEL =$(NSA) del
13 | NSA_MOD =$(NSA) mod -s
14 | NSA_DIFF =$(NSA) diff
15 | NSA_DROP =$(NSA) drop
16 | NSA_COMMIT=$(NSA) commit
17 | NSA_DO =$(NSA) do
18 | NSA_LS =$(NSA) ls
19 | NSA_REVERT=$(NSA) revert
20 | NSA_COMMIT=$(NSA) commit
21 |
22 | PASS =echo -n "lol"
23 | PASS2 =echo -ne "lol\nlol"
24 | PASS_NEW =echo -n "rofl"
25 | PASS_NEW2=echo -ne "rofl\nrofl"
26 | PASS_USR =echo -ne "lel\nlel"
27 |
28 | KDF=argon2 nsapasssalt -d -k 1048576 -p 4 -t 20 -r
29 | DEC=$(KDF) | openssl enc -chacha20 -in=$(CUR)/$(DB) -d -kfile=- -iter=1
30 | CLN=python ./striptimestamps.py
31 | DIFF=diff --color=always -u
32 |
33 | GR="\033[1;32m"
34 | NC="\033[0m"
35 | PREFIX="\n\n>>>>>>>> "
36 |
37 | .PHONY: test
38 | test: clean
39 | @echo -e $(GR)$(PREFIX)prepare tests..$(NC)
40 | mkdir -p $(CUR) $(IPC)
41 | @echo -e $(GR)$(PREFIX)test docs..$(NC)
42 | $(NSA) -h > $(CUR)/-h
43 | $(NSA) -v > $(CUR)/-v
44 | $(DIFF) $(TRU)/-h $(CUR)/-h
45 | $(DIFF) $(TRU)/-v $(CUR)/-v
46 | @echo -e $(GR)$(PREFIX)test create..$(NC)
47 | $(PASS2) | $(NSA_CREATE)
48 | $(PASS) | $(DEC) > $(CUR)/$(DB).create.str
49 | $(CLN) $(CUR)/$(DB).create.str > $(CUR)/$(DB).create.str.cln
50 | $(DIFF) $(TRU)/$(DB).create.str.cln $(CUR)/$(DB).create.str.cln
51 | @echo -e $(GR)$(PREFIX)test start..$(NC)
52 | $(PASS) | $(NSA_START)
53 | sleep 10
54 | @echo -e $(GR)$(PREFIX)test ignorable actions..$(NC)
55 | $(NSA_DIFF)
56 | $(NSA_DROP)
57 | $(NSA_COMMIT)
58 | @echo -e $(GR)$(PREFIX)test add without a commit..$(NC)
59 | $(NSA_ADD) -t tag1 tag2 -n "oy vey" -f $(TRU)/data
60 | $(NSA_ADD) -t test asd dea s
61 | $(NSA_ADD) -t test
62 | $(PASS_USR) | $(NSA_ADD) -t asdfa lel -m
63 | $(PASS) | $(DEC) > $(CUR)/$(DB).add_wo_commit.str
64 | $(CLN) $(CUR)/$(DB).add_wo_commit.str > $(CUR)/$(DB).add_wo_commit.str.cln
65 | $(DIFF) $(TRU)/$(DB).add_wo_commit.str.cln $(CUR)/$(DB).add_wo_commit.str.cln
66 | @echo -e $(GR)$(PREFIX)test restart midways..$(NC)
67 | $(NSA_STOP)
68 | $(PASS) | $(NSA_START)
69 | sleep 10
70 | @echo -e $(GR)$(PREFIX)test commit the pending add..$(NC)
71 | $(NSA_COMMIT)
72 | $(PASS) | $(DEC) > $(CUR)/$(DB).add.str
73 | $(CLN) $(CUR)/$(DB).add.str > $(CUR)/$(DB).add.str.cln
74 | $(DIFF) $(TRU)/$(DB).add.str.cln $(CUR)/$(DB).add.str.cln
75 | @echo -e $(GR)$(PREFIX)test del without a commit..$(NC)
76 | $(NSA_DEL) test
77 | $(PASS) | $(DEC) > $(CUR)/$(DB).del_wo_commit.str
78 | $(CLN) $(CUR)/$(DB).del_wo_commit.str > $(CUR)/$(DB).del_wo_commit.str.cln
79 | $(DIFF) $(TRU)/$(DB).del_wo_commit.str.cln $(CUR)/$(DB).del_wo_commit.str.cln
80 | @echo -e $(GR)$(PREFIX)test restart midways..$(NC)
81 | $(NSA_STOP)
82 | $(PASS) | $(NSA_START)
83 | sleep 10
84 | @echo -e $(GR)$(PREFIX)test commit the pending del..$(NC)
85 | $(NSA_COMMIT)
86 | $(PASS) | $(DEC) > $(CUR)/$(DB).del.str
87 | $(CLN) $(CUR)/$(DB).del.str > $(CUR)/$(DB).del.str.cln
88 | $(DIFF) $(TRU)/$(DB).del.str.cln $(CUR)/$(DB).del.str.cln
89 | @echo -e $(GR)$(PREFIX)test mod without a commit..$(NC)
90 | $(NSA_MOD) t t -u barak -r https://mossad.gov.il
91 | $(NSA_MOD) t d -f $(TRU)/data
92 | $(PASS_USR) | $(NSA_MOD) t t -m
93 | $(PASS) | $(DEC) > $(CUR)/$(DB).mod_wo_commit.str
94 | $(CLN) $(CUR)/$(DB).mod_wo_commit.str > $(CUR)/$(DB).mod_wo_commit.str.cln
95 | $(DIFF) $(TRU)/$(DB).mod_wo_commit.str.cln $(CUR)/$(DB).mod_wo_commit.str.cln
96 | @echo -e $(GR)$(PREFIX)test restart midways..$(NC)
97 | $(NSA_STOP)
98 | $(PASS) | $(NSA_START)
99 | sleep 10
100 | @echo -e $(GR)$(PREFIX)test commit the pending mod..$(NC)
101 | $(NSA_COMMIT)
102 | $(PASS) | $(DEC) > $(CUR)/$(DB).mod.str
103 | $(CLN) $(CUR)/$(DB).mod.str > $(CUR)/$(DB).mod.str.cln
104 | $(DIFF) $(TRU)/$(DB).mod.str.cln $(CUR)/$(DB).mod.str.cln
105 | @echo -e $(GR)$(PREFIX)test do..$(NC)
106 | $(NSA_DO) t t -c cat > $(CUR)/data-t_t
107 | $(NSA_DO) t d -c cat > $(CUR)/data-t_d
108 | $(DIFF) $(TRU)/data $(CUR)/data-t_t
109 | $(DIFF) $(TRU)/data $(CUR)/data-t_d
110 | @echo -e $(GR)$(PREFIX)test ls..$(NC)
111 | $(NSA_LS)
112 | @echo -e $(GR)$(PREFIX)test chpass..$(NC)
113 | $(PASS_NEW2) | $(NSA_CHPASS)
114 | $(NSA_COMMIT)
115 | $(NSA_STOP)
116 | $(PASS_NEW) | $(NSA_START)
117 | sleep 5
118 | @echo -e $(GR)$(PREFIX)test stop..$(NC)
119 | $(NSA_STOP)
120 |
121 | .PHONY: clean
122 | clean:
123 | # clean
124 | rm -rf $(CUR) $(IPC)
125 |
--------------------------------------------------------------------------------
/tests/striptimestamps.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import json
3 | input_file = sys.argv[1]
4 | with open(input_file, 'r') as f:
5 | db = json.load(f)
6 | for tags in db:
7 | del db[tags]['timestamp']
8 | print(json.dumps(db, indent=4))
9 |
--------------------------------------------------------------------------------
/tests/truth/-h:
--------------------------------------------------------------------------------
1 | usage: nsa [-h] [-v] [-V] [-C] [-i DIR]
2 | {create,chpass,start,stop,do,add,del,mod,ls,diff,commit,drop} ...
3 |
4 | options:
5 | -h, --help show this help message and exit
6 | -v show information about nsapass
7 | -V show debugging information
8 | -C disable colourful output
9 | -i DIR ipc directory
10 |
11 | commands:
12 | {create,chpass,start,stop,do,add,del,mod,ls,diff,commit,drop}
13 | create create a databases
14 | chpass change databases's password
15 | start starts nsapass
16 | stop stops nsapass and discards any uncommitted changes
17 | do do things (e.g. type passwords)
18 | add add an entry
19 | del delete an entry
20 | mod modify an entry
21 | ls view entries
22 | diff show modifications done so far
23 | commit commit changes to the database
24 | drop discard all uncommitted changed
25 |
--------------------------------------------------------------------------------
/tests/truth/-v:
--------------------------------------------------------------------------------
1 | nsapass v10 copyright (C) 2022 caveman
2 | https://github.com/Al-Caveman/nsapass
3 |
4 | this program comes with ABSOLUTELY NO WARRANTY; for details
5 | read https://github.com/Al-Caveman/nsapass/blob/master/LICENSE
6 |
--------------------------------------------------------------------------------
/tests/truth/data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/al-caveman/nsapass/982d99be4a66cc50e9f6856e9b4fec89f227a8b2/tests/truth/data
--------------------------------------------------------------------------------
/tests/truth/test_db.add.str.cln:
--------------------------------------------------------------------------------
1 | {
2 | "tag1 tag2": {
3 | "username": null,
4 | "password": null,
5 | "datab64s": "AVRi7QPOIDF0FmxaxTBTjga33bY21iTavJQ7nyccemiRJpJko1iN/2NoTaLdXCqPG5FbgcA2fCOYZqFbhVogl+Xeub9sJ17IbGV8suMdyv36MZfq36Sqfeko1zcpEgsXd8m6cW0czniFZO1BvAjqafMGaGcWo5RooyW3c0rYzgpCJw+yFrjHadB/SEpaEE1jJq9T7lcYf1wbzTnc4Rn7hR4I5gBX31Mxv/lQilLagE76B16AW5hNjdECdCmen+vaWxm+MNNV+Lq/FE2t83BB8+8vOcd0y1LXB6KH7cBz7nFA7V5n8BzLwf7X/qWUOOhnpUMdTl1KTFFgwgvZzjw61ExOWyKR/TM0IVr7qCWW9+BdpYouv2FK7poTIMx5O88rYUnnLWKU7NykMkmX5kJVCzCpHehNOQAa9ihaq5KVJkf9Pq1xS5YejNVl9yJF9ut7T22UKMFToVitaT+//L6Jx15cug4vQIu2KpFtSJ2GW+b5xMyytWIj4dyrjWLJw4QcEwT1Vn8sOrL3mi6UZnQoIiWN3l+WVarswSzFKjyU/MdVkOPIUVv2ntUOorGw88MgkZyQ+GcHUbryFiEpb7Dvzc+Zb+GigfPcnHAcXJh5FWQNvINROftIcr/2c1XGqGq9MQN0miH179g5SgxZe/uBwWKqsL+cngE6NH0AtYXfOEvyT9PsB8070E1aJf0hBH5jRlnv1svJV6fX4N78aNz+1NXfNP4rq59B3qbqFB8i3yL/PPk8Z6dPpRn28H7P06QOEkM5ON12Nts+Yo49QrFkmnzSw1rrqlQSCUF9qA5T/W7m68gogVYLjfrERvm9MMso90iIkLscDPF7hrpaPJZUMfRRqUzmtz8zff0HOcQPq5TA/1amfPwLzChwTrBJQo/Qb1BaSHjEqPk2TZi9MoRBjhAuSoE2xSuY9q58jMeBsEGyp0Bm1g03l3kmc8SP4WUQJIqCNkSgKr+fMJ/D00euXeT/Cn029IMGGFbbf3PkPkjjA7icqh5mHtqyEnwNtq7ud1mzFHtbs81Vvx4HAaYvu92sm44Tq2eat8kAjJcP3VaN2Dx3QEHsIUjZCMWaUhm2Y04Q5rpyNn0G5nhsiTlKSqp8j2VKgBLPEgo0zTkH6mgTL4Qz5rZFebEIlj/4TZzxPyz/aWw0M50fIAA+Zf+MaCUNSrJd7xkrujn2vg2s+9q9K6wbL1Cplfc6GHQEj2AcViiFxWWxRr2ZmVbfLQa4bDLZpFq7jEEUd9Doa+/eweey9EwI/gYAR6SdSe/BpiZEbUNPqkZts3/E9AQgTlSlV1YetnovsgcoyRpmSoVh7OkKBG/uvOOFBrB5Qs+utFerf6bbvgnVde6Nv/hXawPv/Q==",
6 | "digest": "8c2b82f0d28afad29d48a2565cf866f34af65f32d2178c369e77d747",
7 | "uri": null,
8 | "note": "oy vey"
9 | },
10 | "test asd dea s": {
11 | "username": null,
12 | "password": null,
13 | "datab64s": null,
14 | "digest": null,
15 | "uri": null,
16 | "note": null
17 | },
18 | "test": {
19 | "username": null,
20 | "password": null,
21 | "datab64s": null,
22 | "digest": null,
23 | "uri": null,
24 | "note": null
25 | },
26 | "asdfa lel": {
27 | "username": null,
28 | "password": "lel",
29 | "datab64s": null,
30 | "digest": null,
31 | "uri": null,
32 | "note": null
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/truth/test_db.create.str.cln:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tests/truth/test_db.del.str.cln:
--------------------------------------------------------------------------------
1 | {
2 | "tag1 tag2": {
3 | "username": null,
4 | "password": null,
5 | "datab64s": "AVRi7QPOIDF0FmxaxTBTjga33bY21iTavJQ7nyccemiRJpJko1iN/2NoTaLdXCqPG5FbgcA2fCOYZqFbhVogl+Xeub9sJ17IbGV8suMdyv36MZfq36Sqfeko1zcpEgsXd8m6cW0czniFZO1BvAjqafMGaGcWo5RooyW3c0rYzgpCJw+yFrjHadB/SEpaEE1jJq9T7lcYf1wbzTnc4Rn7hR4I5gBX31Mxv/lQilLagE76B16AW5hNjdECdCmen+vaWxm+MNNV+Lq/FE2t83BB8+8vOcd0y1LXB6KH7cBz7nFA7V5n8BzLwf7X/qWUOOhnpUMdTl1KTFFgwgvZzjw61ExOWyKR/TM0IVr7qCWW9+BdpYouv2FK7poTIMx5O88rYUnnLWKU7NykMkmX5kJVCzCpHehNOQAa9ihaq5KVJkf9Pq1xS5YejNVl9yJF9ut7T22UKMFToVitaT+//L6Jx15cug4vQIu2KpFtSJ2GW+b5xMyytWIj4dyrjWLJw4QcEwT1Vn8sOrL3mi6UZnQoIiWN3l+WVarswSzFKjyU/MdVkOPIUVv2ntUOorGw88MgkZyQ+GcHUbryFiEpb7Dvzc+Zb+GigfPcnHAcXJh5FWQNvINROftIcr/2c1XGqGq9MQN0miH179g5SgxZe/uBwWKqsL+cngE6NH0AtYXfOEvyT9PsB8070E1aJf0hBH5jRlnv1svJV6fX4N78aNz+1NXfNP4rq59B3qbqFB8i3yL/PPk8Z6dPpRn28H7P06QOEkM5ON12Nts+Yo49QrFkmnzSw1rrqlQSCUF9qA5T/W7m68gogVYLjfrERvm9MMso90iIkLscDPF7hrpaPJZUMfRRqUzmtz8zff0HOcQPq5TA/1amfPwLzChwTrBJQo/Qb1BaSHjEqPk2TZi9MoRBjhAuSoE2xSuY9q58jMeBsEGyp0Bm1g03l3kmc8SP4WUQJIqCNkSgKr+fMJ/D00euXeT/Cn029IMGGFbbf3PkPkjjA7icqh5mHtqyEnwNtq7ud1mzFHtbs81Vvx4HAaYvu92sm44Tq2eat8kAjJcP3VaN2Dx3QEHsIUjZCMWaUhm2Y04Q5rpyNn0G5nhsiTlKSqp8j2VKgBLPEgo0zTkH6mgTL4Qz5rZFebEIlj/4TZzxPyz/aWw0M50fIAA+Zf+MaCUNSrJd7xkrujn2vg2s+9q9K6wbL1Cplfc6GHQEj2AcViiFxWWxRr2ZmVbfLQa4bDLZpFq7jEEUd9Doa+/eweey9EwI/gYAR6SdSe/BpiZEbUNPqkZts3/E9AQgTlSlV1YetnovsgcoyRpmSoVh7OkKBG/uvOOFBrB5Qs+utFerf6bbvgnVde6Nv/hXawPv/Q==",
6 | "digest": "8c2b82f0d28afad29d48a2565cf866f34af65f32d2178c369e77d747",
7 | "uri": null,
8 | "note": "oy vey"
9 | },
10 | "test asd dea s": {
11 | "username": null,
12 | "password": null,
13 | "datab64s": null,
14 | "digest": null,
15 | "uri": null,
16 | "note": null
17 | },
18 | "asdfa lel": {
19 | "username": null,
20 | "password": "lel",
21 | "datab64s": null,
22 | "digest": null,
23 | "uri": null,
24 | "note": null
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/truth/test_db.mod.str.cln:
--------------------------------------------------------------------------------
1 | {
2 | "tag1 tag2": {
3 | "username": "barak",
4 | "password": "lel",
5 | "datab64s": "AVRi7QPOIDF0FmxaxTBTjga33bY21iTavJQ7nyccemiRJpJko1iN/2NoTaLdXCqPG5FbgcA2fCOYZqFbhVogl+Xeub9sJ17IbGV8suMdyv36MZfq36Sqfeko1zcpEgsXd8m6cW0czniFZO1BvAjqafMGaGcWo5RooyW3c0rYzgpCJw+yFrjHadB/SEpaEE1jJq9T7lcYf1wbzTnc4Rn7hR4I5gBX31Mxv/lQilLagE76B16AW5hNjdECdCmen+vaWxm+MNNV+Lq/FE2t83BB8+8vOcd0y1LXB6KH7cBz7nFA7V5n8BzLwf7X/qWUOOhnpUMdTl1KTFFgwgvZzjw61ExOWyKR/TM0IVr7qCWW9+BdpYouv2FK7poTIMx5O88rYUnnLWKU7NykMkmX5kJVCzCpHehNOQAa9ihaq5KVJkf9Pq1xS5YejNVl9yJF9ut7T22UKMFToVitaT+//L6Jx15cug4vQIu2KpFtSJ2GW+b5xMyytWIj4dyrjWLJw4QcEwT1Vn8sOrL3mi6UZnQoIiWN3l+WVarswSzFKjyU/MdVkOPIUVv2ntUOorGw88MgkZyQ+GcHUbryFiEpb7Dvzc+Zb+GigfPcnHAcXJh5FWQNvINROftIcr/2c1XGqGq9MQN0miH179g5SgxZe/uBwWKqsL+cngE6NH0AtYXfOEvyT9PsB8070E1aJf0hBH5jRlnv1svJV6fX4N78aNz+1NXfNP4rq59B3qbqFB8i3yL/PPk8Z6dPpRn28H7P06QOEkM5ON12Nts+Yo49QrFkmnzSw1rrqlQSCUF9qA5T/W7m68gogVYLjfrERvm9MMso90iIkLscDPF7hrpaPJZUMfRRqUzmtz8zff0HOcQPq5TA/1amfPwLzChwTrBJQo/Qb1BaSHjEqPk2TZi9MoRBjhAuSoE2xSuY9q58jMeBsEGyp0Bm1g03l3kmc8SP4WUQJIqCNkSgKr+fMJ/D00euXeT/Cn029IMGGFbbf3PkPkjjA7icqh5mHtqyEnwNtq7ud1mzFHtbs81Vvx4HAaYvu92sm44Tq2eat8kAjJcP3VaN2Dx3QEHsIUjZCMWaUhm2Y04Q5rpyNn0G5nhsiTlKSqp8j2VKgBLPEgo0zTkH6mgTL4Qz5rZFebEIlj/4TZzxPyz/aWw0M50fIAA+Zf+MaCUNSrJd7xkrujn2vg2s+9q9K6wbL1Cplfc6GHQEj2AcViiFxWWxRr2ZmVbfLQa4bDLZpFq7jEEUd9Doa+/eweey9EwI/gYAR6SdSe/BpiZEbUNPqkZts3/E9AQgTlSlV1YetnovsgcoyRpmSoVh7OkKBG/uvOOFBrB5Qs+utFerf6bbvgnVde6Nv/hXawPv/Q==",
6 | "digest": "8c2b82f0d28afad29d48a2565cf866f34af65f32d2178c369e77d747",
7 | "uri": "https://mossad.gov.il",
8 | "note": "oy vey"
9 | },
10 | "test asd dea s": {
11 | "username": null,
12 | "password": null,
13 | "datab64s": "AVRi7QPOIDF0FmxaxTBTjga33bY21iTavJQ7nyccemiRJpJko1iN/2NoTaLdXCqPG5FbgcA2fCOYZqFbhVogl+Xeub9sJ17IbGV8suMdyv36MZfq36Sqfeko1zcpEgsXd8m6cW0czniFZO1BvAjqafMGaGcWo5RooyW3c0rYzgpCJw+yFrjHadB/SEpaEE1jJq9T7lcYf1wbzTnc4Rn7hR4I5gBX31Mxv/lQilLagE76B16AW5hNjdECdCmen+vaWxm+MNNV+Lq/FE2t83BB8+8vOcd0y1LXB6KH7cBz7nFA7V5n8BzLwf7X/qWUOOhnpUMdTl1KTFFgwgvZzjw61ExOWyKR/TM0IVr7qCWW9+BdpYouv2FK7poTIMx5O88rYUnnLWKU7NykMkmX5kJVCzCpHehNOQAa9ihaq5KVJkf9Pq1xS5YejNVl9yJF9ut7T22UKMFToVitaT+//L6Jx15cug4vQIu2KpFtSJ2GW+b5xMyytWIj4dyrjWLJw4QcEwT1Vn8sOrL3mi6UZnQoIiWN3l+WVarswSzFKjyU/MdVkOPIUVv2ntUOorGw88MgkZyQ+GcHUbryFiEpb7Dvzc+Zb+GigfPcnHAcXJh5FWQNvINROftIcr/2c1XGqGq9MQN0miH179g5SgxZe/uBwWKqsL+cngE6NH0AtYXfOEvyT9PsB8070E1aJf0hBH5jRlnv1svJV6fX4N78aNz+1NXfNP4rq59B3qbqFB8i3yL/PPk8Z6dPpRn28H7P06QOEkM5ON12Nts+Yo49QrFkmnzSw1rrqlQSCUF9qA5T/W7m68gogVYLjfrERvm9MMso90iIkLscDPF7hrpaPJZUMfRRqUzmtz8zff0HOcQPq5TA/1amfPwLzChwTrBJQo/Qb1BaSHjEqPk2TZi9MoRBjhAuSoE2xSuY9q58jMeBsEGyp0Bm1g03l3kmc8SP4WUQJIqCNkSgKr+fMJ/D00euXeT/Cn029IMGGFbbf3PkPkjjA7icqh5mHtqyEnwNtq7ud1mzFHtbs81Vvx4HAaYvu92sm44Tq2eat8kAjJcP3VaN2Dx3QEHsIUjZCMWaUhm2Y04Q5rpyNn0G5nhsiTlKSqp8j2VKgBLPEgo0zTkH6mgTL4Qz5rZFebEIlj/4TZzxPyz/aWw0M50fIAA+Zf+MaCUNSrJd7xkrujn2vg2s+9q9K6wbL1Cplfc6GHQEj2AcViiFxWWxRr2ZmVbfLQa4bDLZpFq7jEEUd9Doa+/eweey9EwI/gYAR6SdSe/BpiZEbUNPqkZts3/E9AQgTlSlV1YetnovsgcoyRpmSoVh7OkKBG/uvOOFBrB5Qs+utFerf6bbvgnVde6Nv/hXawPv/Q==",
14 | "digest": "8c2b82f0d28afad29d48a2565cf866f34af65f32d2178c369e77d747",
15 | "uri": null,
16 | "note": null
17 | },
18 | "asdfa lel": {
19 | "username": null,
20 | "password": "lel",
21 | "datab64s": null,
22 | "digest": null,
23 | "uri": null,
24 | "note": null
25 | }
26 | }
27 |
--------------------------------------------------------------------------------