commit 960a606ff2b8f63f0b3d2b926ebf3d9c7ec5a838 Author: Luca Bilke Date: Wed Sep 28 09:39:54 2022 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7353e7e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +*.o diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c7aea18 --- /dev/null +++ b/LICENSE @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bbef800 --- /dev/null +++ b/Makefile @@ -0,0 +1,66 @@ +# pinentry-dmenu - dmenu-like stupid pin entry +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = pinentry-dmenu.c drw.c util.c +OBJ = ${SRC:.c=.o} +OBJ_PIN = pinentry/pinentry.o pinentry/util.o pinentry/password-cache.o pinentry/argparse.o pinentry/secmem.o + +all: options pinentry-dmenu + +options: + @echo pinentry-dmenu build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +${OBJ}: config.h config.mk drw.h + +pinentry: + $(MAKE) -C pinentry + +pinentry-dmenu: pinentry pinentry-dmenu.o drw.o util.o + @echo CC -o $@ + @${CC} -o $@ ${OBJ} ${OBJ_PIN} ${LDFLAGS} -lassuan -lgpgme -lgpg-error -lconfig + +clean: + @echo cleaning + @rm -f pinentry-dmenu ${OBJ} + $(MAKE) -C pinentry/ clean + +dist: clean + @echo creating dist tarball + @mkdir -p dmenu-${VERSION} + @cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1 \ + drw.h util.h dmenu_path dmenu_run stest.1 ${SRC} \ + dmenu-${VERSION} + @tar -cf dmenu-${VERSION}.tar dmenu-${VERSION} + @gzip dmenu-${VERSION}.tar + @rm -rf dmenu-${VERSION} + +install: all + @echo installing executable to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f pinentry-dmenu ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/pinentry-dmenu + @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 + @mkdir -p ${DESTDIR}${MANPREFIX}/man1 + @sed "s/VERSION/${VERSION}/g;s/DATE/${DATE}/g;s/BUGREPORT/${BUGREPORT}/g" < pinentry-dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/pinentry-dmenu.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/pinentry-dmenu.1 + +uninstall: + @echo removing executable from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/pinentry-dmenu + @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/pinentry-dmenu.1 + +.PHONY: all options clean dist install pinentry uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..12815ca --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +pinentry-dmenu +============== + +pinentry-dmenu is a pinentry program with the charm of [dmenu](https://tools.suckless.org/dmenu). + +This program is a fork from [spine](https://gitgud.io/zavok/spine.git) which is also a fork from [dmenu](https://tools.suckless.org/dmenu). + + +Requirements +------------ +In order to build dmenu you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (dmenu is installed into the /usr/local namespace by default). + +Afterwards enter the following command to build and install dmenu +(if necessary as root): + + make clean install + + +Config +------ +To use pinentry-dmenu add in `~/.gnupg/gpg-agent.conf`: + + pinentry-program + +The config is located in `~/.gnupg/pinentry-dmenu.conf`. + +Parameter | Default | Description +:------------------ |:----------------- |:----------- +asterisk | * | Defines the symbol which is showed for each typed character +bottom | false | pinentry-dmenu appears at the bottom of the screen +min_password_length | 32 | The minimal space of the password field. This value has affect to the description field after the password field +monitor | -1 | pinentry-dmenu is displayed on the monitor number supplied. Monitor numbers are starting from 0 +prompt | "" | Defines the prompt to be displayed to the left of the input field +font | monospace:size=10 | Defines the font or font set used +prompt_bg | #bbbbbb | Defines the prompt background color +prompt_fg | #222222 | Defines the prompt foreground color +normal_bg | #bbbbbb | Defines the normal background color +normal_fg | #222222 | Defines the normal foreground color +select_bg | #eeeeee | Defines the selected background color +select_fg | #005577 | Defines the selected foreground color +desc_bg | #bbbbbb | Defines the description background color +desc_fg | #222222 | Defines the description foreground color +embedded | false | Embed into window + + +Example +------- +``` +asterisk= "# "; +prompt = "$"; +font = "Noto Sans UI:size=13"; +prompt_fg = "#eeeeee"; +prompt_bg = "#d9904a"; +normal_fg = "#ffffff"; +normal_bg = "#000000"; +select_fg = "#eeeeee"; +select_bg = "#d9904a"; +desc_fg = "#eeeeee"; +desc_bg = "#d9904a"; +``` diff --git a/config.h b/config.h new file mode 100644 index 0000000..4a3eb60 --- /dev/null +++ b/config.h @@ -0,0 +1,21 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int bottom = 0; +static int embedded = 0; +static int minpwlen = 32; +static int mon = -1; +static int lineheight = 0; +static int min_lineheight = 8; + +static const char *asterisk = "ï‘„"; +static const char *fonts[] = { + "NotoSans Nerd Font:style=Regular:pixelsize=14:antialias=true" +}; +static const char *prompt = "ï …"; +static const char *colors[SchemeLast][4] = { + [SchemePrompt] = { "#c0caf5", "#15161E" }, + [SchemeNormal] = { "#c0caf5", "#15161E" }, + [SchemeSelect] = { "#15161E", "#7aa2f7" }, + [SchemeDesc] = { "#a9b1d6", "#15161E" } +}; diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..4c71211 --- /dev/null +++ b/config.mk @@ -0,0 +1,33 @@ +# Pinentry settings +DATE = $$(date +'%B %Y') +VERSION = 0.1 +BUGREPORT = https:\/\/github.com\/ritze\/pinentry-dmenu + +# Paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# Freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = ${X11INC}/freetype2 + +# Includes and libs +INCS = -I${X11INC} -I${FREETYPEINC} +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} + +# Flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} -DPACKAGE_VERSION=\"${VERSION}\" -DPACKAGE_BUGREPORT=\"${BUGREPORT}\" +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} +LDFLAGS = -s ${LIBS} + +# Compiler and linker +CC = cc diff --git a/drw.c b/drw.c new file mode 100644 index 0000000..c1582e7 --- /dev/null +++ b/drw.c @@ -0,0 +1,421 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + char buf[1024]; + int ty; + unsigned int ew; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + size_t i, len; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); + /* shorten text if necessary */ + for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + + if (len) { + memcpy(buf, utf8str, len); + buf[len] = '\0'; + if (len < utf8strlen) + for (i = len; i && i > len - 3; buf[--i] = '.') + ; /* NOP */ + + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, len); + } + x += ew; + w -= ew; + } + } + + if (!*text) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/drw.h b/drw.h new file mode 100644 index 0000000..4c67419 --- /dev/null +++ b/drw.h @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/pinentry-dmenu.1 b/pinentry-dmenu.1 new file mode 100644 index 0000000..67aafd1 --- /dev/null +++ b/pinentry-dmenu.1 @@ -0,0 +1,228 @@ +.TH PINENTRY-DMENU 1 "DATE" pinentry-dmenu\-VERSION "pinentry-dmenu Manual" + + +.SH NAME +pinentry-dmenu - a pinentry program with the charm of dmenu +.SH DESCRIPTION +.B pinentry-dmenu +is a dmenu- and pinentry-based passphrase dialog called from the +.BR gpg-agent (1) +daemon. It is not intended to be invoked directly. + + +.SH SYNOPSIS +Set the +.B pinentry-program +in +.IR ~/.gnupg/gpg-agent.conf +to +.B pinentry-dmenu +to use the program as the regular dialog for +.BR gpg-agent . +.PP +The configuration is placed in +.IR ~/.gnupg/pinentry-dmenu.conf . +You can change the path to the config file with the environment variable +.IR GNUPGHOME . + + +.SH OPTIONS +.TP +.BI "asterisk =" " *" +Defines the symbol which is showed for each typed character. +.TP +.BI "bottom =" " false" +pinentry-dmenu appears at the bottom of the screen. +.TP +.BI "min_password_length =" " 32" +The minimal space of the password field. This value has affect to the description field after the password field. +.TP +.BI "height =" " 8" +Height of pinentry-dmenu in pixels (no less than 8) +.TP +.BI "monitor =" " -1" +pinentry-dmenu is displayed on the monitor number supplied. Monitor numbers are starting from 0. +.TP +.BI "prompt =" " """" +Defines the prompt to be displayed to the left of the input field. +.TP +.BI "font =" " monospace:size=10" +Defines the font or font set used. +.TP +.BI "prompt_bg =" " #bbbbbb" +Defines the prompt background color. +.IR #RGB , +.I #RRGGBB +and X color names are supported. +.TP +.BI "prompt_fg =" " #222222" +Defines the prompt foreground color. +.TP +.BI "normal_bg =" " #bbbbbb" +Defines the normal background color. +.TP +.BI "normal_fg =" " #222222" +Defines the normal foreground color. +.TP +.BI "select_bg =" " #eeeeee" +Defines the selected background color. +.TP +.BI "select_fg =" " #005577" +Defines the selected foreground color. +.TP +.BI "desc_bg =" " #bbbbbb" +Defines the description background color. +.TP +.BI "desc_fg =" " #222222" +Defines the description foreground color. +.TP +.BI "embedded =" " false" +Embed into window. + + +.SH USAGE +pinentry-dmenu is completely controlled by the keyboard. +.TP +.B Return +Confirm input +.TP +.B Ctrl-Return +Confirm input +.TP +.B Shift\-Return +Confirm input +.TP +.B Escape +Cancel input +.TP +.B C\-c +Escape + +.SS Confirm Mode +.TP +.B Down +Right +.TP +.B End +Right +.TP +.B Home +Left +.TP +.B Next +Right +.TP +.B Prior +Left +.TP +.B Up +Left +.TP +.B g +Cancel input +.TP +.B G +Cancel input +.TP +.B h +Left +.TP +.B j +Left +.TP +.B k +Right +.TP +.B l +Right +.TP +.B n +Confirm with no +.TP +.B N +Confirm with no +.TP +.B y +Confirm with yes +.TP +.B Y +Confirm with yes + +.SS Pin Mode +.TP +.B End +Move cursor to the line end +.TP +.B Home +Move cursor to the line begin +.TP +.B C\-a +Home +.TP +.B C\-b +Left +.TP +.B C\-d +Delete +.TP +.B C\-e +End +.TP +.B C\-f +Right +.TP +.B C\-g +Escape +.TP +.B C\-h +Backspace +.TP +.B C\-k +Delete line right +.TP +.B C\-u +Delete line left +.TP +.B C\-v +Paste from primary X selection + + +.SH EXAMPLES +.sp +.if n \{ +.RS 4 +.\} +.nf +asterisk= "# "; +prompt = "$"; +font = "Noto Sans UI:size=13"; +prompt_fg = "#eeeeee"; +prompt_bg = "#d9904a"; +normal_fg = "#ffffff"; +normal_bg = "#000000"; +select_fg = "#eeeeee"; +select_bg = "#d9904a"; +desc_fg = "#eeeeee"; +desc_bg = "#d9904a"; + + +.SH AUTHORS +.B pinentry-dmenu +is a fork of +.B dmenu + +and uses the api of +.B pinentry +, a GnuPG tool. +.B pinentry-dmenu +itself was written by Moritz Lüdecke . + + +.SH REPORTING BUGS +Report pinentry-dmenu bugs to + + +.SH SEE ALSO +.BR dmenu (1), +.BR dwm (1), +.BR gpg-agent (1) diff --git a/pinentry-dmenu.c b/pinentry-dmenu.c new file mode 100644 index 0000000..3086b43 --- /dev/null +++ b/pinentry-dmenu.c @@ -0,0 +1,803 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include + +#include "drw.h" +#include "util.h" + +#include "pinentry/pinentry.h" +#include "pinentry/memory.h" + +#define CONFIG_DIR "/.gnupg" +#define CONFIG_FILE "/pinentry-dmenu.conf" +#define INTERSECT(x, y, w, h, r) \ + (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ + && MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) +#define LENGTH(X) (sizeof(X) / sizeof(X[0])) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) +#define MINDESCLEN 8 + + +enum { SchemePrompt, SchemeNormal, SchemeSelect, SchemeDesc, SchemeLast }; +enum { WinPin, WinConfirm }; +enum { Ok, NotOk, Cancel }; +enum { Nothing, Yes, No }; + +static int bh, mw, mh; +static int sel; +static int promptw, pdescw; +/* Sum of left and right padding */ +static int lrpad; +static size_t cursor; +static int screen; + +static char* pin; +static int pin_len; +static char* pin_repeat; +static int pin_repeat_len; +static int repeat; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +static int timed_out; +static int winmode; +pinentry_t pinentry_info; + +#include "config.h" + +static int +drawitem(const char* text, Bool sel, int x, int y, int w) { + unsigned int i = (sel) ? SchemeSelect : SchemeNormal; + + drw_setscheme(drw, scheme[i]); + + return drw_text(drw, x, y, w, bh, lrpad / 2, text, 0); +} + +static void +grabfocus(void) { + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) { + return; + } + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + usleep(1000); + } + + die("cannot grab focus"); +} + +static void +grabkeyboard(void) { + int i; + + if (embedded) { + return; + } + + /* Try to grab keyboard, + * we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) { + return; + } + usleep(1000); + } + + die("cannot grab keyboard"); +} + +static size_t +nextrune(int cursor, int inc) { + ssize_t n; + + /* Return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = cursor + inc; + n + inc >= 0 && (pin[n] & 0xc0) == 0x80; + n += inc); + + return n; +} + +static void +setup_pin(char* pin_ptr, int len, int reset) { + pin = pin_ptr; + pin_len = len; + + if (reset) { + promptw = (prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + cursor = 0; + + if (pin) { + pin[0] = '\0'; + } + } +} + +static void +insert(const char *str, ssize_t n) { + size_t len = strlen(pin); + + // FIXME: Pinentry crashes when increasing the pin buffer the second time. + // Other pinentry programs has a limited passwort field length. + if (len + n > pin_len - 1) { + if (repeat) { + pin_repeat_len = 2 * pin_repeat_len; + pin_repeat = secmem_realloc(pin_repeat, pin_repeat_len); + setup_pin(pin_repeat, pin_repeat_len, 0); + if (!pin_repeat) { + pin_len = 0; + } + } else { + if (!pinentry_setbufferlen(pinentry_info, 2 * pinentry_info->pin_len)) { + pin_len = 0; + } else { + setup_pin(pinentry_info->pin, pinentry_info->pin_len, 0); + } + } + if (pin_len == 0) { + printf("Error: Couldn't allocate secure memory\n"); + return; + } + } + + /* Move existing text out of the way, insert new text, and update cursor */ + memmove(&pin[cursor + n], &pin[cursor], pin_len - cursor - MAX(n, 0)); + + if (n > 0) { + memcpy(&pin[cursor], str, n); + } + + cursor += n; + pin[len + n] = '\0'; +} + +static void +drawwin(void) { + unsigned int curpos; + int x = 0, fh = drw->fonts->h, pb, pbw = 0, i; + size_t asterlen = strlen(asterisk); + size_t pdesclen; + int leftinput; + char* censort; + + char* pprompt = (repeat) ? pinentry_info->repeat_passphrase : pinentry_info->prompt; + int ppromptw = (pprompt) ? TEXTW(pprompt) : 0; + + unsigned int censortl = minpwlen * TEXTW(asterisk) / strlen(asterisk); + unsigned int confirml = TEXTW(" YesNo ") + 3 * lrpad; + + drw_setscheme(drw, scheme[SchemeNormal]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt) { + drw_setscheme(drw, scheme[SchemePrompt]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + + if (pprompt) { + drw_setscheme(drw, scheme[SchemePrompt]); + drw_text(drw, x, 0, ppromptw, bh, lrpad / 2, pprompt, 0); + x += ppromptw; + } + + if (pinentry_info->description) { + pb = mw - x; + pdesclen = strlen(pinentry_info->description); + + if (pb > 0) { + pb -= (winmode == WinPin) ? censortl : confirml; + pbw = MINDESCLEN * pdescw / pdesclen; + pbw = MIN(pbw, pdescw); + + if (pb >= pbw) { + pbw = MAX(pbw, pdescw); + pbw = MIN(pbw, pb); + pb = mw - pbw; + + for (i = 0; i < pdesclen; i++) { + if (pinentry_info->description[i] == '\n') { + pinentry_info->description[i] = ' '; + } + } + + drw_setscheme(drw, scheme[SchemeDesc]); + drw_text(drw, pb, 0, pbw, bh, lrpad / 2, pinentry_info->description, + 0); + } else { + pbw = 0; + } + } + } + + /* Draw input field */ + drw_setscheme(drw, scheme[SchemeNormal]); + + if (winmode == WinPin) { + censort = ecalloc(1, asterlen * pin_len); + + for (i = 0; i < asterlen * strlen(pin); i += asterlen) { + memcpy(&censort[i], asterisk, asterlen); + } + + censort[i+1] = '\n'; + leftinput = mw - x - pbw; + drw_text(drw, x, 0, leftinput, bh, lrpad / 2, censort, 0); + drw_font_getexts(drw->fonts, censort, cursor * asterlen, &curpos, NULL); + + if ((curpos += lrpad / 2 - 1) < leftinput) { + drw_setscheme(drw, scheme[SchemeNormal]); + drw_rect(drw, x + curpos, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0); + } + + free(censort); + } else { + x += TEXTW(" "); + x = drawitem("No", (sel == No), x, 0, TEXTW("No")); + x = drawitem("Yes", (sel == Yes), x, 0, TEXTW("Yes")); + } + + drw_map(drw, win, 0, 0, mw, mh); +} + +static void +setup(void) { + int x, y, i = 0; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, j, di, n, area = 0; +#endif + + /* Init appearance */ + scheme[SchemePrompt] = drw_scm_create(drw, colors[SchemePrompt], 2); + scheme[SchemeNormal] = drw_scm_create(drw, colors[SchemeNormal], 2); + scheme[SchemeSelect] = drw_scm_create(drw, colors[SchemeSelect], 2); + scheme[SchemeDesc] = drw_scm_create(drw, colors[SchemeDesc], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* Calculate menu geometry */ + bh = drw->fonts->h + 2; + bh = MAX(bh, lineheight); + mh = bh; +#ifdef XINERAMA + info = XineramaQueryScreens(dpy, &n); + + if (parentwin == root && info) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) { + i = mon; + } else if (w != root && w != PointerRoot && w != None) { + /* Find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) { + XFree(dws); + } + } while (w != root && w != pw); + /* Find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) { + for (j = 0; j < n; j++) { + a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j]); + if (a > area) { + area = a; + i = j; + } + } + } + } + /* No focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area + && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) { + for (i = 0; i < n; i++) { + if (INTERSECT(x, y, 1, 1, info[i])) { + break; + } + } + } + + x = info[i].x_org; + y = info[i].y_org + (bottom ? info[i].height - mh : 0); + mw = info[i].width; + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) { + die("could not get embedding window attributes: 0x%lx", parentwin); + } + x = 0; + y = bottom ? wa.height - mh : 0; + mw = wa.width; + } + + pdescw = (pinentry_info->description) ? TEXTW(pinentry_info->description) : 0; + + /* Create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemePrompt][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + + /* Open input methods */ + xim = XOpenIM(dpy, NULL, NULL, NULL); + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + XMapRaised(dpy, win); + + if (embedded) { + XSelectInput(dpy, parentwin, FocusChangeMask); + + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) { + XSelectInput(dpy, dws[i], FocusChangeMask); + } + + XFree(dws); + } + grabfocus(); + } + + drw_resize(drw, mw, mh); +} + +static void +cleanup(void) { + XUngrabKey(dpy, AnyKey, AnyModifier, root); + free(scheme[SchemeDesc]); + free(scheme[SchemeSelect]); + free(scheme[SchemeNormal]); + free(scheme[SchemePrompt]); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static int +keypress_confirm(XKeyEvent *ev, KeySym ksym) { + if (ev->state & ControlMask) { + switch(ksym) { + case XK_c: + pinentry_info->canceled = 1; + sel = No; + return 1; + default: + return 1; + } + } + + switch(ksym) { + case XK_KP_Enter: + case XK_Return: + if (sel != Nothing) { + return 1; + } + break; + case XK_y: + case XK_Y: + sel = Yes; + return 1; + case XK_n: + case XK_N: + sel = No; + return 1; + case XK_g: + case XK_G: + case XK_Escape: + pinentry_info->canceled = 1; + sel = No; + return 1; + case XK_h: + case XK_j: + case XK_Home: + case XK_Left: + case XK_Prior: + case XK_Up: + sel = No; + break; + case XK_k: + case XK_l: + case XK_Down: + case XK_End: + case XK_Next: + case XK_Right: + sel = Yes; + break; + } + + return 0; +} + +static int +keypress_pin(XKeyEvent *ev, KeySym ksym, char* buf, int len) { + int old; + + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_k: + old = cursor; + cursor = strlen(pin); + insert(NULL, old - cursor); + break; + case XK_u: + insert(NULL, -cursor); + break; + case XK_v: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return 0; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + pinentry_info->canceled = 1; + return 1; + default: + return 1; + } + } + + switch(ksym) { + case XK_Delete: + if (pin[cursor] == '\0') { + return 0; + } + cursor = nextrune(cursor, +1); + /* Fallthrough */ + case XK_BackSpace: + if (cursor == 0) { + return 0; + } + insert(NULL, nextrune(cursor, -1) - cursor); + break; + case XK_Escape: + pinentry_info->canceled = 1; + return 1; + case XK_Left: + if (cursor > 0) { + cursor = nextrune(cursor, -1); + } + break; + case XK_Right: + if (pin[cursor] != '\0') { + cursor = nextrune(cursor, +1); + } + break; + case XK_Home: + cursor = 0; + break; + case XK_End: + cursor = strlen(pin); + break; + case XK_Return: + case XK_KP_Enter: + return 1; + break; + default: + if (!iscntrl(*buf)) { + insert(buf, len); + } + } + + return 0; +} + +static int +keypress(XKeyEvent *ev) { + char buf[32]; + int len; + int ret = 1; + + KeySym ksym = NoSymbol; + Status status; + len = XmbLookupString(xic, ev, buf, sizeof(buf), &ksym, &status); + + if (status != XBufferOverflow) { + if (winmode == WinConfirm) { + ret = keypress_confirm(ev, ksym); + } else { + ret = keypress_pin(ev, ksym, buf, len); + } + + if (ret == 0) { + drawwin(); + } + } + + return ret; +} + +static void +paste(void) { + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* We have been given the current selection, now insert it into input */ + XGetWindowProperty(dpy, win, utf8, 0, pin_len / 4, False, utf8, &da, &di, + &dl, &dl, (unsigned char **)&p); + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t) strlen(p)); + XFree(p); + drawwin(); +} + +void +run(void) { + XEvent ev; + + drawwin(); + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) { + continue; + } + switch(ev.type) { + case Expose: + if (ev.xexpose.count == 0) { + drw_map(drw, win, 0, 0, mw, mh); + } + break; + case KeyPress: + if (keypress(&ev.xkey)) { + return; + } + break; + case SelectionNotify: + if (ev.xselection.property == utf8) { + paste(); + } + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) { + XRaiseWindow(dpy, win); + } + break; + } + } +} + +static void +catchsig(int sig) { + if (sig == SIGALRM) { + timed_out = 1; + } +} + +static void +password(void) { + winmode = WinPin; + repeat = 0; + setup_pin(pinentry_info->pin, pinentry_info->pin_len, 1); + run(); + + if (!pinentry_info->canceled && pinentry_info->repeat_passphrase) { + repeat = 1; + pin_repeat_len = pinentry_info->pin_len; + pin_repeat = secmem_malloc(pinentry_info->pin_len); + setup_pin(pin_repeat, pin_repeat_len, 1); + run(); + + pinentry_info->repeat_okay = (strcmp(pinentry_info->pin, pin_repeat) == 0)? 1 : 0; + secmem_free(pin_repeat); + + if (!pinentry_info->repeat_okay) { + pinentry_info->result = -1; + return; + } + } + + if (pinentry_info->canceled) { + pinentry_info->result = -1; + return; + } + + pinentry_info->result = strlen(pinentry_info->pin); +} + +static void +confirm(void) { + winmode = WinConfirm; + sel = Nothing; + run(); + pinentry_info->result = sel != No; +} + +static int +cmdhandler(pinentry_t received_pinentry) { + struct sigaction sa; + XWindowAttributes wa; + + pinentry_info = received_pinentry; + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) { + fputs("warning: no locale support\n", stderr); + } + if (!(dpy = XOpenDisplay(pinentry_info->display))) { + die("cannot open display"); + } + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + embedded = (pinentry_info->parent_wid) ? embedded : 0; + parentwin = (embedded) ? pinentry_info->parent_wid : root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) { + die("could not get embedding window attributes: 0x%lx", parentwin); + } + drw = drw_create(dpy, screen, root, wa.width, wa.height); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) { + die("no fonts could be loaded."); + } + lrpad = drw->fonts->h; + drw_setscheme(drw, scheme[SchemePrompt]); + + if (pinentry_info->timeout) { + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = catchsig; + sigaction(SIGALRM, &sa, NULL); + alarm(pinentry_info->timeout); + } + + grabkeyboard(); + setup(); + + if (pinentry_info->pin) { + do { + password(); + } while (!pinentry_info->canceled && pinentry_info->repeat_passphrase + && !pinentry_info->repeat_okay); + } else { + confirm(); + } + + cleanup(); + + return pinentry_info->result; +} + +pinentry_cmd_handler_t pinentry_cmd_handler = cmdhandler; + +int +main(int argc, char *argv[]) { + Bool bval; + int i, val; + const char *str; + struct passwd *pw; + char path[PATH_MAX]; + char *sudo_uid = getenv("SUDO_UID"); + char *home = getenv("HOME"); + char *gnupghome = getenv("GNUPGHOME"); + config_t cfg; + + if (gnupghome) { + i = strlen(gnupghome); + strcpy(path, gnupghome); + } else { + /* Get the home dir even if the user used sudo or logged in as root */ + if (sudo_uid) { + i = atoi(sudo_uid); + pw = getpwuid(i); + home = pw->pw_dir; + } + + i = strlen(home); + strcpy(path, home); + strcpy(&path[i], CONFIG_DIR); + i += strlen(CONFIG_DIR); + } + + strcpy(&path[i], CONFIG_FILE); + endpwent(); + + config_init(&cfg); + + /* Read the file. If there is an error, report it and exit. */ + if (config_read_file(&cfg, path)) { + if (config_lookup_string(&cfg, "asterisk", &str)) { + asterisk = str; + } + if (config_lookup_bool(&cfg, "bottom", &bval)) { + bottom = bval; + } + if (config_lookup_int(&cfg, "min_password_length", &val)) { + minpwlen = val; + } + if (config_lookup_int(&cfg, "height", &val)) { + lineheight = MAX(val, min_lineheight); + } + if (config_lookup_int(&cfg, "monitor", &val)) { + mon = val; + } + if (config_lookup_string(&cfg, "prompt", &str)) { + prompt = str; + } + if (config_lookup_string(&cfg, "font", &str)) { + fonts[0] = str; + } + if (config_lookup_string(&cfg, "prompt_bg", &str)) { + colors[SchemePrompt][ColBg] = str; + } + if (config_lookup_string(&cfg, "prompt_fg", &str)) { + colors[SchemePrompt][ColFg] = str; + } + if (config_lookup_string(&cfg, "normal_bg", &str)) { + colors[SchemeNormal][ColBg] = str; + } + if (config_lookup_string(&cfg, "normal_fg", &str)) { + colors[SchemeNormal][ColFg] = str; + } + if (config_lookup_string(&cfg, "select_bg", &str)) { + colors[SchemeSelect][ColBg] = str; + } + if (config_lookup_string(&cfg, "select_fg", &str)) { + colors[SchemeSelect][ColFg] = str; + } + if (config_lookup_string(&cfg, "desc_bg", &str)) { + colors[SchemeDesc][ColBg] = str; + } + if (config_lookup_string(&cfg, "desc_fg", &str)) { + colors[SchemeDesc][ColFg] = str; + } + if (config_lookup_bool(&cfg, "embedded", &bval)) { + embedded = bval; + } + } else if ((str = config_error_file(&cfg))) { + fprintf(stderr, "%s:%d: %s\n", config_error_file(&cfg), + config_error_line(&cfg), config_error_text(&cfg)); + return(EXIT_FAILURE); + } + + pinentry_init("pinentry-dmenu"); + pinentry_parse_opts(argc, argv); + + if (pinentry_loop()) { + return 1; + } + + config_destroy(&cfg); + + return 0; +} diff --git a/pinentry/AUTHORS b/pinentry/AUTHORS new file mode 100644 index 0000000..695d0ba --- /dev/null +++ b/pinentry/AUTHORS @@ -0,0 +1,11 @@ +Program: Pinentry +Bug reports: +Security related bug reports: +License: GPLv2+ + +Robert Bihlmeyer +Werner Koch, g10 Code GmbH +Steffen Hansen, Klarälvdalens Datakonsult AB +Marcus Brinkmann, g10 Code GmbH +Timo Schulz, g10 Code GmbH +Neal Walfied, g10 Code GmbH diff --git a/pinentry/COPYING b/pinentry/COPYING new file mode 100644 index 0000000..c7aea18 --- /dev/null +++ b/pinentry/COPYING @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/pinentry/Makefile b/pinentry/Makefile new file mode 100644 index 0000000..4de2470 --- /dev/null +++ b/pinentry/Makefile @@ -0,0 +1,21 @@ +include ../config.mk + +SRC = util.c pinentry.c argparse.c password-cache.c secmem.c +OBJ = ${SRC:.c=.o} +CFLAGS += -DHAVE_MLOCK + +all: pinentry + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: pinentry.h argparse.h password-cache.h memory.h util.h + +pinentry: pinentry.o argparse.o password-cache.o secmem.o util.o + +clean: + @echo cleaning + @rm -f ${OBJ} + +.PHONY: all clean pinentry diff --git a/pinentry/argparse.c b/pinentry/argparse.c new file mode 100644 index 0000000..e31b67e --- /dev/null +++ b/pinentry/argparse.c @@ -0,0 +1,1607 @@ +/* [argparse.c wk 17.06.97] Argument Parser for option handling + * Copyright (C) 1998-2001, 2006-2008, 2012 Free Software Foundation, Inc. + * Copyright (C) 1997-2001, 2006-2008, 2013-2015 Werner Koch + * + * This file is part of JNLIB, which is a subsystem of GnuPG. + * + * JNLIB is free software; you can redistribute it and/or modify it + * under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * or both in parallel, as here. + * + * JNLIB is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copies of the GNU General Public License + * and the GNU Lesser General Public License along with this program; + * if not, see . + */ + +/* This file may be used as part of GnuPG or standalone. A GnuPG + build is detected by the presence of the macro GNUPG_MAJOR_VERSION. + Some feature are only availalbe in the GnuPG build mode. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef GNUPG_MAJOR_VERSION +# include "libjnlib-config.h" +# include "mischelp.h" +# include "stringhelp.h" +# include "logging.h" +# ifdef JNLIB_NEED_UTF8CONV +# include "utf8conv.h" +# endif +#endif /*GNUPG_MAJOR_VERSION*/ + +#include "argparse.h" + +/* GnuPG uses GPLv3+ but a standalone version of this defaults to + GPLv2+ because that is the license of this file. Change this if + you include it in a program which uses GPLv3. If you don't want to + set a a copyright string for your usage() you may also hardcode it + here. */ +#ifndef GNUPG_MAJOR_VERSION + +# define ARGPARSE_GPL_VERSION 2 +# define ARGPARSE_CRIGHT_STR "Copyright (C) YEAR NAME" + +#else /* Used by GnuPG */ + +# define ARGPARSE_GPL_VERSION 3 +# define ARGPARSE_CRIGHT_STR "Copyright (C) 2015 Free Software Foundation, Inc." + +#endif /*GNUPG_MAJOR_VERSION*/ + +/* Replacements for standalone builds. */ +#ifndef GNUPG_MAJOR_VERSION +# ifndef _ +# define _(a) (a) +# endif +# ifndef DIM +# define DIM(v) (sizeof(v)/sizeof((v)[0])) +# endif +# define jnlib_malloc(a) malloc ((a)) +# define jnlib_realloc(a,b) realloc ((a), (b)) +# define jnlib_strdup(a) strdup ((a)) +# define jnlib_free(a) free ((a)) +# define jnlib_log_error my_log_error +# define jnlib_log_bug my_log_bug +# define trim_spaces(a) my_trim_spaces ((a)) +# define map_static_macro_string(a) (a) +#endif /*!GNUPG_MAJOR_VERSION*/ + + +#define ARGPARSE_STR(v) #v +#define ARGPARSE_STR2(v) ARGPARSE_STR(v) + + +/* Replacements for standalone builds. */ +#ifndef GNUPG_MAJOR_VERSION +static void +my_log_error (const char *fmt, ...) +{ + va_list arg_ptr ; + + va_start (arg_ptr, fmt); + fprintf (stderr, "%s: ", strusage (11)); + vfprintf (stderr, fmt, arg_ptr); + va_end (arg_ptr); +} + +static void +my_log_bug (const char *fmt, ...) +{ + va_list arg_ptr ; + + va_start (arg_ptr, fmt); + fprintf (stderr, "%s: Ohhhh jeeee: ", strusage (11)); + vfprintf (stderr, fmt, arg_ptr); + va_end (arg_ptr); + abort (); +} + +static char * +my_trim_spaces (char *str) +{ + char *string, *p, *mark; + + string = str; + /* Find first non space character. */ + for (p=string; *p && isspace (*(unsigned char*)p) ; p++) + ; + /* Move characters. */ + for ((mark = NULL); (*string = *p); string++, p++) + if (isspace (*(unsigned char*)p)) + { + if (!mark) + mark = string; + } + else + mark = NULL; + if (mark) + *mark = '\0' ; /* Remove trailing spaces. */ + + return str ; +} + +#endif /*!GNUPG_MAJOR_VERSION*/ + + + +/********************************* + * @Summary arg_parse + * #include "argparse.h" + * + * typedef struct { + * char *argc; pointer to argc (value subject to change) + * char ***argv; pointer to argv (value subject to change) + * unsigned flags; Global flags (DO NOT CHANGE) + * int err; print error about last option + * 1 = warning, 2 = abort + * int r_opt; return option + * int r_type; type of return value (0 = no argument found) + * union { + * int ret_int; + * long ret_long + * ulong ret_ulong; + * char *ret_str; + * } r; Return values + * struct { + * int idx; + * const char *last; + * void *aliases; + * } internal; DO NOT CHANGE + * } ARGPARSE_ARGS; + * + * typedef struct { + * int short_opt; + * const char *long_opt; + * unsigned flags; + * } ARGPARSE_OPTS; + * + * int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts ); + * + * @Description + * This is my replacement for getopt(). See the example for a typical usage. + * Global flags are: + * Bit 0 : Do not remove options form argv + * Bit 1 : Do not stop at last option but return other args + * with r_opt set to -1. + * Bit 2 : Assume options and real args are mixed. + * Bit 3 : Do not use -- to stop option processing. + * Bit 4 : Do not skip the first arg. + * Bit 5 : allow usage of long option with only one dash + * Bit 6 : ignore --version + * all other bits must be set to zero, this value is modified by the + * function, so assume this is write only. + * Local flags (for each option): + * Bit 2-0 : 0 = does not take an argument + * 1 = takes int argument + * 2 = takes string argument + * 3 = takes long argument + * 4 = takes ulong argument + * Bit 3 : argument is optional (r_type will the be set to 0) + * Bit 4 : allow 0x etc. prefixed values. + * Bit 6 : Ignore this option + * Bit 7 : This is a command and not an option + * You stop the option processing by setting opts to NULL, the function will + * then return 0. + * @Return Value + * Returns the args.r_opt or 0 if ready + * r_opt may be -2/-7 to indicate an unknown option/command. + * @See Also + * ArgExpand + * @Notes + * You do not need to process the options 'h', '--help' or '--version' + * because this function includes standard help processing; but if you + * specify '-h', '--help' or '--version' you have to do it yourself. + * The option '--' stops argument processing; if bit 1 is set the function + * continues to return normal arguments. + * To process float args or unsigned args you must use a string args and do + * the conversion yourself. + * @Example + * + * ARGPARSE_OPTS opts[] = { + * { 'v', "verbose", 0 }, + * { 'd', "debug", 0 }, + * { 'o', "output", 2 }, + * { 'c', "cross-ref", 2|8 }, + * { 'm', "my-option", 1|8 }, + * { 300, "ignored-long-option, ARGPARSE_OP_IGNORE}, + * { 500, "have-no-short-option-for-this-long-option", 0 }, + * {0} }; + * ARGPARSE_ARGS pargs = { &argc, &argv, 0 } + * + * while( ArgParse( &pargs, &opts) ) { + * switch( pargs.r_opt ) { + * case 'v': opt.verbose++; break; + * case 'd': opt.debug++; break; + * case 'o': opt.outfile = pargs.r.ret_str; break; + * case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; + * case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; + * case 500: opt.a_long_one++; break + * default : pargs.err = 1; break; -- force warning output -- + * } + * } + * if( argc > 1 ) + * log_fatal( "Too many args"); + * + */ + +typedef struct alias_def_s *ALIAS_DEF; +struct alias_def_s { + ALIAS_DEF next; + char *name; /* malloced buffer with name, \0, value */ + const char *value; /* ptr into name */ +}; + + +/* Object to store the names for the --ignore-invalid-option option. + This is a simple linked list. */ +typedef struct iio_item_def_s *IIO_ITEM_DEF; +struct iio_item_def_s +{ + IIO_ITEM_DEF next; + char name[1]; /* String with the long option name. */ +}; + +static const char *(*strusage_handler)( int ) = NULL; +static int (*custom_outfnc) (int, const char *); + +static int set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s); +static void show_help(ARGPARSE_OPTS *opts, unsigned flags); +static void show_version(void); +static int writestrings (int is_error, const char *string, ...) +#if __GNUC__ >= 4 + __attribute__ ((sentinel(0))) +#endif + ; + + +void +argparse_register_outfnc (int (*fnc)(int, const char *)) +{ + custom_outfnc = fnc; +} + + +/* Write STRING and all following const char * arguments either to + stdout or, if IS_ERROR is set, to stderr. The list of strings must + be terminated by a NULL. */ +static int +writestrings (int is_error, const char *string, ...) +{ + va_list arg_ptr; + const char *s; + int count = 0; + + if (string) + { + s = string; + va_start (arg_ptr, string); + do + { + if (custom_outfnc) + custom_outfnc (is_error? 2:1, s); + else + fputs (s, is_error? stderr : stdout); + count += strlen (s); + } + while ((s = va_arg (arg_ptr, const char *))); + va_end (arg_ptr); + } + return count; +} + + +static void +flushstrings (int is_error) +{ + if (custom_outfnc) + custom_outfnc (is_error? 2:1, NULL); + else + fflush (is_error? stderr : stdout); +} + + +static void +initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno ) +{ + if( !(arg->flags & (1<<15)) ) + { + /* Initialize this instance. */ + arg->internal.idx = 0; + arg->internal.last = NULL; + arg->internal.inarg = 0; + arg->internal.stopped = 0; + arg->internal.aliases = NULL; + arg->internal.cur_alias = NULL; + arg->internal.iio_list = NULL; + arg->err = 0; + arg->flags |= 1<<15; /* Mark as initialized. */ + if ( *arg->argc < 0 ) + jnlib_log_bug ("invalid argument for arg_parse\n"); + } + + + if (arg->err) + { + /* Last option was erroneous. */ + const char *s; + + if (filename) + { + if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG ) + s = _("argument not expected"); + else if ( arg->r_opt == ARGPARSE_READ_ERROR ) + s = _("read error"); + else if ( arg->r_opt == ARGPARSE_KEYWORD_TOO_LONG ) + s = _("keyword too long"); + else if ( arg->r_opt == ARGPARSE_MISSING_ARG ) + s = _("missing argument"); + else if ( arg->r_opt == ARGPARSE_INVALID_ARG ) + s = _("invalid argument"); + else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND ) + s = _("invalid command"); + else if ( arg->r_opt == ARGPARSE_INVALID_ALIAS ) + s = _("invalid alias definition"); + else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE ) + s = _("out of core"); + else + s = _("invalid option"); + jnlib_log_error ("%s:%u: %s\n", filename, *lineno, s); + } + else + { + s = arg->internal.last? arg->internal.last:"[??]"; + + if ( arg->r_opt == ARGPARSE_MISSING_ARG ) + jnlib_log_error (_("missing argument for option \"%.50s\"\n"), s); + else if ( arg->r_opt == ARGPARSE_INVALID_ARG ) + jnlib_log_error (_("invalid argument for option \"%.50s\"\n"), s); + else if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG ) + jnlib_log_error (_("option \"%.50s\" does not expect an " + "argument\n"), s ); + else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND ) + jnlib_log_error (_("invalid command \"%.50s\"\n"), s); + else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_OPTION ) + jnlib_log_error (_("option \"%.50s\" is ambiguous\n"), s); + else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_COMMAND ) + jnlib_log_error (_("command \"%.50s\" is ambiguous\n"),s ); + else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE ) + jnlib_log_error ("%s\n", _("out of core\n")); + else + jnlib_log_error (_("invalid option \"%.50s\"\n"), s); + } + if (arg->err != ARGPARSE_PRINT_WARNING) + exit (2); + arg->err = 0; + } + + /* Zero out the return value union. */ + arg->r.ret_str = NULL; + arg->r.ret_long = 0; +} + + +static void +store_alias( ARGPARSE_ARGS *arg, char *name, char *value ) +{ + /* TODO: replace this dummy function with a rea one + * and fix the probelms IRIX has with (ALIAS_DEV)arg.. + * used as lvalue + */ + (void)arg; + (void)name; + (void)value; +#if 0 + ALIAS_DEF a = jnlib_xmalloc( sizeof *a ); + a->name = name; + a->value = value; + a->next = (ALIAS_DEF)arg->internal.aliases; + (ALIAS_DEF)arg->internal.aliases = a; +#endif +} + + +/* Return true if KEYWORD is in the ignore-invalid-option list. */ +static int +ignore_invalid_option_p (ARGPARSE_ARGS *arg, const char *keyword) +{ + IIO_ITEM_DEF item = arg->internal.iio_list; + + for (; item; item = item->next) + if (!strcmp (item->name, keyword)) + return 1; + return 0; +} + + +/* Add the keywords up to the next LF to the list of to be ignored + options. After returning FP will either be at EOF or the next + character read wll be the first of a new line. The function + returns 0 on success or true on malloc failure. */ +static int +ignore_invalid_option_add (ARGPARSE_ARGS *arg, FILE *fp) +{ + IIO_ITEM_DEF item; + int c; + char name[100]; + int namelen = 0; + int ready = 0; + enum { skipWS, collectNAME, skipNAME, addNAME} state = skipWS; + + while (!ready) + { + c = getc (fp); + if (c == '\n') + ready = 1; + else if (c == EOF) + { + c = '\n'; + ready = 1; + } + again: + switch (state) + { + case skipWS: + if (!isascii (c) || !isspace(c)) + { + namelen = 0; + state = collectNAME; + goto again; + } + break; + + case collectNAME: + if (isspace (c)) + { + state = addNAME; + goto again; + } + else if (namelen < DIM(name)-1) + name[namelen++] = c; + else /* Too long. */ + state = skipNAME; + break; + + case skipNAME: + if (isspace (c)) + { + state = skipWS; + goto again; + } + break; + + case addNAME: + name[namelen] = 0; + if (!ignore_invalid_option_p (arg, name)) + { + item = jnlib_malloc (sizeof *item + namelen); + if (!item) + return 1; + strcpy (item->name, name); + item->next = (IIO_ITEM_DEF)arg->internal.iio_list; + arg->internal.iio_list = item; + } + state = skipWS; + goto again; + } + } + return 0; +} + + +/* Clear the entire ignore-invalid-option list. */ +static void +ignore_invalid_option_clear (ARGPARSE_ARGS *arg) +{ + IIO_ITEM_DEF item, tmpitem; + + for (item = arg->internal.iio_list; item; item = tmpitem) + { + tmpitem = item->next; + jnlib_free (item); + } + arg->internal.iio_list = NULL; +} + + + +/**************** + * Get options from a file. + * Lines starting with '#' are comment lines. + * Syntax is simply a keyword and the argument. + * Valid keywords are all keywords from the long_opt list without + * the leading dashes. The special keywords "help", "warranty" and "version" + * are not valid here. + * The special keyword "alias" may be used to store alias definitions, + * which are later expanded like long options. + * The option + * ignore-invalid-option OPTIONNAMEs + * is recognized and updates a list of option which should be ignored if they + * are not defined. + * Caller must free returned strings. + * If called with FP set to NULL command line args are parse instead. + * + * Q: Should we allow the syntax + * keyword = value + * and accept for boolean options a value of 1/0, yes/no or true/false? + * Note: Abbreviation of options is here not allowed. + */ +int +optfile_parse (FILE *fp, const char *filename, unsigned *lineno, + ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) +{ + int state, i, c; + int idx=0; + char keyword[100]; + char *buffer = NULL; + size_t buflen = 0; + int in_alias=0; + + if (!fp) /* Divert to to arg_parse() in this case. */ + return arg_parse (arg, opts); + + initialize (arg, filename, lineno); + + /* Find the next keyword. */ + state = i = 0; + for (;;) + { + c = getc (fp); + if (c == '\n' || c== EOF ) + { + if ( c != EOF ) + ++*lineno; + if (state == -1) + break; + else if (state == 2) + { + keyword[i] = 0; + for (i=0; opts[i].short_opt; i++ ) + { + if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword)) + break; + } + idx = i; + arg->r_opt = opts[idx].short_opt; + if ((opts[idx].flags & ARGPARSE_OPT_IGNORE)) + { + state = i = 0; + continue; + } + else if (!opts[idx].short_opt ) + { + if (!strcmp (keyword, "ignore-invalid-option")) + { + /* No argument - ignore this meta option. */ + state = i = 0; + continue; + } + else if (ignore_invalid_option_p (arg, keyword)) + { + /* This invalid option is in the iio list. */ + state = i = 0; + continue; + } + arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND) + ? ARGPARSE_INVALID_COMMAND + : ARGPARSE_INVALID_OPTION); + } + else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) + arg->r_type = 0; /* Does not take an arg. */ + else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL) ) + arg->r_type = 0; /* Arg is optional. */ + else + arg->r_opt = ARGPARSE_MISSING_ARG; + + break; + } + else if (state == 3) + { + /* No argument found. */ + if (in_alias) + arg->r_opt = ARGPARSE_MISSING_ARG; + else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) + arg->r_type = 0; /* Does not take an arg. */ + else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL)) + arg->r_type = 0; /* No optional argument. */ + else + arg->r_opt = ARGPARSE_MISSING_ARG; + + break; + } + else if (state == 4) + { + /* Has an argument. */ + if (in_alias) + { + if (!buffer) + arg->r_opt = ARGPARSE_UNEXPECTED_ARG; + else + { + char *p; + + buffer[i] = 0; + p = strpbrk (buffer, " \t"); + if (p) + { + *p++ = 0; + trim_spaces (p); + } + if (!p || !*p) + { + jnlib_free (buffer); + arg->r_opt = ARGPARSE_INVALID_ALIAS; + } + else + { + store_alias (arg, buffer, p); + } + } + } + else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) + arg->r_opt = ARGPARSE_UNEXPECTED_ARG; + else + { + char *p; + + if (!buffer) + { + keyword[i] = 0; + buffer = jnlib_strdup (keyword); + if (!buffer) + arg->r_opt = ARGPARSE_OUT_OF_CORE; + } + else + buffer[i] = 0; + + if (buffer) + { + trim_spaces (buffer); + p = buffer; + if (*p == '"') + { + /* Remove quotes. */ + p++; + if (*p && p[strlen(p)-1] == '\"' ) + p[strlen(p)-1] = 0; + } + if (!set_opt_arg (arg, opts[idx].flags, p)) + jnlib_free(buffer); + } + } + break; + } + else if (c == EOF) + { + ignore_invalid_option_clear (arg); + if (ferror (fp)) + arg->r_opt = ARGPARSE_READ_ERROR; + else + arg->r_opt = 0; /* EOF. */ + break; + } + state = 0; + i = 0; + } + else if (state == -1) + ; /* Skip. */ + else if (state == 0 && isascii (c) && isspace(c)) + ; /* Skip leading white space. */ + else if (state == 0 && c == '#' ) + state = 1; /* Start of a comment. */ + else if (state == 1) + ; /* Skip comments. */ + else if (state == 2 && isascii (c) && isspace(c)) + { + /* Check keyword. */ + keyword[i] = 0; + for (i=0; opts[i].short_opt; i++ ) + if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword)) + break; + idx = i; + arg->r_opt = opts[idx].short_opt; + if ((opts[idx].flags & ARGPARSE_OPT_IGNORE)) + { + state = 1; /* Process like a comment. */ + } + else if (!opts[idx].short_opt) + { + if (!strcmp (keyword, "alias")) + { + in_alias = 1; + state = 3; + } + else if (!strcmp (keyword, "ignore-invalid-option")) + { + if (ignore_invalid_option_add (arg, fp)) + { + arg->r_opt = ARGPARSE_OUT_OF_CORE; + break; + } + state = i = 0; + ++*lineno; + } + else if (ignore_invalid_option_p (arg, keyword)) + state = 1; /* Process like a comment. */ + else + { + arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND) + ? ARGPARSE_INVALID_COMMAND + : ARGPARSE_INVALID_OPTION); + state = -1; /* Skip rest of line and leave. */ + } + } + else + state = 3; + } + else if (state == 3) + { + /* Skip leading spaces of the argument. */ + if (!isascii (c) || !isspace(c)) + { + i = 0; + keyword[i++] = c; + state = 4; + } + } + else if (state == 4) + { + /* Collect the argument. */ + if (buffer) + { + if (i < buflen-1) + buffer[i++] = c; + else + { + char *tmp; + size_t tmplen = buflen + 50; + + tmp = jnlib_realloc (buffer, tmplen); + if (tmp) + { + buflen = tmplen; + buffer = tmp; + buffer[i++] = c; + } + else + { + jnlib_free (buffer); + arg->r_opt = ARGPARSE_OUT_OF_CORE; + break; + } + } + } + else if (i < DIM(keyword)-1) + keyword[i++] = c; + else + { + size_t tmplen = DIM(keyword) + 50; + buffer = jnlib_malloc (tmplen); + if (buffer) + { + buflen = tmplen; + memcpy(buffer, keyword, i); + buffer[i++] = c; + } + else + { + arg->r_opt = ARGPARSE_OUT_OF_CORE; + break; + } + } + } + else if (i >= DIM(keyword)-1) + { + arg->r_opt = ARGPARSE_KEYWORD_TOO_LONG; + state = -1; /* Skip rest of line and leave. */ + } + else + { + keyword[i++] = c; + state = 2; + } + } + + return arg->r_opt; +} + + + +static int +find_long_option( ARGPARSE_ARGS *arg, + ARGPARSE_OPTS *opts, const char *keyword ) +{ + int i; + size_t n; + + (void)arg; + + /* Would be better if we can do a binary search, but it is not + possible to reorder our option table because we would mess + up our help strings - What we can do is: Build a nice option + lookup table wehn this function is first invoked */ + if( !*keyword ) + return -1; + for(i=0; opts[i].short_opt; i++ ) + if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) ) + return i; +#if 0 + { + ALIAS_DEF a; + /* see whether it is an alias */ + for( a = args->internal.aliases; a; a = a->next ) { + if( !strcmp( a->name, keyword) ) { + /* todo: must parse the alias here */ + args->internal.cur_alias = a; + return -3; /* alias available */ + } + } + } +#endif + /* not found, see whether it is an abbreviation */ + /* aliases may not be abbreviated */ + n = strlen( keyword ); + for(i=0; opts[i].short_opt; i++ ) { + if( opts[i].long_opt && !strncmp( opts[i].long_opt, keyword, n ) ) { + int j; + for(j=i+1; opts[j].short_opt; j++ ) { + if( opts[j].long_opt + && !strncmp( opts[j].long_opt, keyword, n ) ) + return -2; /* abbreviation is ambiguous */ + } + return i; + } + } + return -1; /* Not found. */ +} + +int +arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) +{ + int idx; + int argc; + char **argv; + char *s, *s2; + int i; + + initialize( arg, NULL, NULL ); + argc = *arg->argc; + argv = *arg->argv; + idx = arg->internal.idx; + + if (!idx && argc && !(arg->flags & ARGPARSE_FLAG_ARG0)) + { + /* Skip the first argument. */ + argc--; argv++; idx++; + } + + next_one: + if (!argc) + { + /* No more args. */ + arg->r_opt = 0; + goto leave; /* Ready. */ + } + + s = *argv; + arg->internal.last = s; + + if (arg->internal.stopped && (arg->flags & ARGPARSE_FLAG_ALL)) + { + arg->r_opt = ARGPARSE_IS_ARG; /* Not an option but an argument. */ + arg->r_type = 2; + arg->r.ret_str = s; + argc--; argv++; idx++; /* set to next one */ + } + else if( arg->internal.stopped ) + { + arg->r_opt = 0; + goto leave; /* Ready. */ + } + else if ( *s == '-' && s[1] == '-' ) + { + /* Long option. */ + char *argpos; + + arg->internal.inarg = 0; + if (!s[2] && !(arg->flags & ARGPARSE_FLAG_NOSTOP)) + { + /* Stop option processing. */ + arg->internal.stopped = 1; + arg->flags |= ARGPARSE_FLAG_STOP_SEEN; + argc--; argv++; idx++; + goto next_one; + } + + argpos = strchr( s+2, '=' ); + if ( argpos ) + *argpos = 0; + i = find_long_option ( arg, opts, s+2 ); + if ( argpos ) + *argpos = '='; + + if ( i < 0 && !strcmp ( "help", s+2) ) + show_help (opts, arg->flags); + else if ( i < 0 && !strcmp ( "version", s+2) ) + { + if (!(arg->flags & ARGPARSE_FLAG_NOVERSION)) + { + show_version (); + exit(0); + } + } + else if ( i < 0 && !strcmp( "warranty", s+2)) + { + writestrings (0, strusage (16), "\n", NULL); + exit (0); + } + else if ( i < 0 && !strcmp( "dump-options", s+2) ) + { + for (i=0; opts[i].short_opt; i++ ) + { + if (opts[i].long_opt && !(opts[i].flags & ARGPARSE_OPT_IGNORE)) + writestrings (0, "--", opts[i].long_opt, "\n", NULL); + } + writestrings (0, "--dump-options\n--help\n--version\n--warranty\n", + NULL); + exit (0); + } + + if ( i == -2 ) + arg->r_opt = ARGPARSE_AMBIGUOUS_OPTION; + else if ( i == -1 ) + { + arg->r_opt = ARGPARSE_INVALID_OPTION; + arg->r.ret_str = s+2; + } + else + arg->r_opt = opts[i].short_opt; + if ( i < 0 ) + ; + else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) ) + { + if ( argpos ) + { + s2 = argpos+1; + if ( !*s2 ) + s2 = NULL; + } + else + s2 = argv[1]; + if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + arg->r_type = ARGPARSE_TYPE_NONE; /* Argument is optional. */ + } + else if ( !s2 ) + { + arg->r_opt = ARGPARSE_MISSING_ARG; + } + else if ( !argpos && *s2 == '-' + && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + /* The argument is optional and the next seems to be an + option. We do not check this possible option but + assume no argument */ + arg->r_type = ARGPARSE_TYPE_NONE; + } + else + { + set_opt_arg (arg, opts[i].flags, s2); + if ( !argpos ) + { + argc--; argv++; idx++; /* Skip one. */ + } + } + } + else + { + /* Does not take an argument. */ + if ( argpos ) + arg->r_type = ARGPARSE_UNEXPECTED_ARG; + else + arg->r_type = 0; + } + argc--; argv++; idx++; /* Set to next one. */ + } + else if ( (*s == '-' && s[1]) || arg->internal.inarg ) + { + /* Short option. */ + int dash_kludge = 0; + + i = 0; + if ( !arg->internal.inarg ) + { + arg->internal.inarg++; + if ( (arg->flags & ARGPARSE_FLAG_ONEDASH) ) + { + for (i=0; opts[i].short_opt; i++ ) + if ( opts[i].long_opt && !strcmp (opts[i].long_opt, s+1)) + { + dash_kludge = 1; + break; + } + } + } + s += arg->internal.inarg; + + if (!dash_kludge ) + { + for (i=0; opts[i].short_opt; i++ ) + if ( opts[i].short_opt == *s ) + break; + } + + if ( !opts[i].short_opt && ( *s == 'h' || *s == '?' ) ) + show_help (opts, arg->flags); + + arg->r_opt = opts[i].short_opt; + if (!opts[i].short_opt ) + { + arg->r_opt = (opts[i].flags & ARGPARSE_OPT_COMMAND)? + ARGPARSE_INVALID_COMMAND:ARGPARSE_INVALID_OPTION; + arg->internal.inarg++; /* Point to the next arg. */ + arg->r.ret_str = s; + } + else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) ) + { + if ( s[1] && !dash_kludge ) + { + s2 = s+1; + set_opt_arg (arg, opts[i].flags, s2); + } + else + { + s2 = argv[1]; + if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + arg->r_type = ARGPARSE_TYPE_NONE; + } + else if ( !s2 ) + { + arg->r_opt = ARGPARSE_MISSING_ARG; + } + else if ( *s2 == '-' && s2[1] + && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + /* The argument is optional and the next seems to + be an option. We do not check this possible + option but assume no argument. */ + arg->r_type = ARGPARSE_TYPE_NONE; + } + else + { + set_opt_arg (arg, opts[i].flags, s2); + argc--; argv++; idx++; /* Skip one. */ + } + } + s = "x"; /* This is so that !s[1] yields false. */ + } + else + { + /* Does not take an argument. */ + arg->r_type = ARGPARSE_TYPE_NONE; + arg->internal.inarg++; /* Point to the next arg. */ + } + if ( !s[1] || dash_kludge ) + { + /* No more concatenated short options. */ + arg->internal.inarg = 0; + argc--; argv++; idx++; + } + } + else if ( arg->flags & ARGPARSE_FLAG_MIXED ) + { + arg->r_opt = ARGPARSE_IS_ARG; + arg->r_type = 2; + arg->r.ret_str = s; + argc--; argv++; idx++; /* Set to next one. */ + } + else + { + arg->internal.stopped = 1; /* Stop option processing. */ + goto next_one; + } + + leave: + *arg->argc = argc; + *arg->argv = argv; + arg->internal.idx = idx; + return arg->r_opt; +} + + +/* Returns: -1 on error, 0 for an integer type and 1 for a non integer + type argument. */ +static int +set_opt_arg (ARGPARSE_ARGS *arg, unsigned flags, char *s) +{ + int base = (flags & ARGPARSE_OPT_PREFIX)? 0 : 10; + long l; + + switch ( (arg->r_type = (flags & ARGPARSE_TYPE_MASK)) ) + { + case ARGPARSE_TYPE_LONG: + case ARGPARSE_TYPE_INT: + errno = 0; + l = strtol (s, NULL, base); + if ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) + { + arg->r_opt = ARGPARSE_INVALID_ARG; + return -1; + } + if (arg->r_type == ARGPARSE_TYPE_LONG) + arg->r.ret_long = l; + else if ( (l < 0 && l < INT_MIN) || l > INT_MAX ) + { + arg->r_opt = ARGPARSE_INVALID_ARG; + return -1; + } + else + arg->r.ret_int = (int)l; + return 0; + + case ARGPARSE_TYPE_ULONG: + while (isascii (*s) && isspace(*s)) + s++; + if (*s == '-') + { + arg->r.ret_ulong = 0; + arg->r_opt = ARGPARSE_INVALID_ARG; + return -1; + } + errno = 0; + arg->r.ret_ulong = strtoul (s, NULL, base); + if (arg->r.ret_ulong == ULONG_MAX && errno == ERANGE) + { + arg->r_opt = ARGPARSE_INVALID_ARG; + return -1; + } + return 0; + + case ARGPARSE_TYPE_STRING: + default: + arg->r.ret_str = s; + return 1; + } +} + + +static size_t +long_opt_strlen( ARGPARSE_OPTS *o ) +{ + size_t n = strlen (o->long_opt); + + if ( o->description && *o->description == '|' ) + { + const char *s; +#ifdef JNLIB_NEED_UTF8CONV + int is_utf8 = is_native_utf8 (); +#endif + + s=o->description+1; + if ( *s != '=' ) + n++; + /* For a (mostly) correct length calculation we exclude + continuation bytes (10xxxxxx) if we are on a native utf8 + terminal. */ + for (; *s && *s != '|'; s++ ) +#ifdef JNLIB_NEED_UTF8CONV + if ( is_utf8 && (*s&0xc0) != 0x80 ) +#endif + n++; + } + return n; +} + + +/**************** + * Print formatted help. The description string has some special + * meanings: + * - A description string which is "@" suppresses help output for + * this option + * - a description,ine which starts with a '@' and is followed by + * any other characters is printed as is; this may be used for examples + * ans such. + * - A description which starts with a '|' outputs the string between this + * bar and the next one as arguments of the long option. + */ +static void +show_help (ARGPARSE_OPTS *opts, unsigned int flags) +{ + const char *s; + char tmp[2]; + + show_version (); + writestrings (0, "\n", NULL); + s = strusage (42); + if (s && *s == '1') + { + s = strusage (40); + writestrings (1, s, NULL); + if (*s && s[strlen(s)] != '\n') + writestrings (1, "\n", NULL); + } + s = strusage(41); + writestrings (0, s, "\n", NULL); + if ( opts[0].description ) + { + /* Auto format the option description. */ + int i,j, indent; + + /* Get max. length of long options. */ + for (i=indent=0; opts[i].short_opt; i++ ) + { + if ( opts[i].long_opt ) + if ( !opts[i].description || *opts[i].description != '@' ) + if ( (j=long_opt_strlen(opts+i)) > indent && j < 35 ) + indent = j; + } + + /* Example: " -v, --verbose Viele Sachen ausgeben" */ + indent += 10; + if ( *opts[0].description != '@' ) + writestrings (0, "Options:", "\n", NULL); + for (i=0; opts[i].short_opt; i++ ) + { + s = map_static_macro_string (_( opts[i].description )); + if ( s && *s== '@' && !s[1] ) /* Hide this line. */ + continue; + if ( s && *s == '@' ) /* Unindented comment only line. */ + { + for (s++; *s; s++ ) + { + if ( *s == '\n' ) + { + if( s[1] ) + writestrings (0, "\n", NULL); + } + else + { + tmp[0] = *s; + tmp[1] = 0; + writestrings (0, tmp, NULL); + } + } + writestrings (0, "\n", NULL); + continue; + } + + j = 3; + if ( opts[i].short_opt < 256 ) + { + tmp[0] = opts[i].short_opt; + tmp[1] = 0; + writestrings (0, " -", tmp, NULL ); + if ( !opts[i].long_opt ) + { + if (s && *s == '|' ) + { + writestrings (0, " ", NULL); j++; + for (s++ ; *s && *s != '|'; s++, j++ ) + { + tmp[0] = *s; + tmp[1] = 0; + writestrings (0, tmp, NULL); + } + if ( *s ) + s++; + } + } + } + else + writestrings (0, " ", NULL); + if ( opts[i].long_opt ) + { + tmp[0] = opts[i].short_opt < 256?',':' '; + tmp[1] = 0; + j += writestrings (0, tmp, " --", opts[i].long_opt, NULL); + if (s && *s == '|' ) + { + if ( *++s != '=' ) + { + writestrings (0, " ", NULL); + j++; + } + for ( ; *s && *s != '|'; s++, j++ ) + { + tmp[0] = *s; + tmp[1] = 0; + writestrings (0, tmp, NULL); + } + if ( *s ) + s++; + } + writestrings (0, " ", NULL); + j += 3; + } + for (;j < indent; j++ ) + writestrings (0, " ", NULL); + if ( s ) + { + if ( *s && j > indent ) + { + writestrings (0, "\n", NULL); + for (j=0;j < indent; j++ ) + writestrings (0, " ", NULL); + } + for (; *s; s++ ) + { + if ( *s == '\n' ) + { + if ( s[1] ) + { + writestrings (0, "\n", NULL); + for (j=0; j < indent; j++ ) + writestrings (0, " ", NULL); + } + } + else + { + tmp[0] = *s; + tmp[1] = 0; + writestrings (0, tmp, NULL); + } + } + } + writestrings (0, "\n", NULL); + } + if ( (flags & ARGPARSE_FLAG_ONEDASH) ) + writestrings (0, "\n(A single dash may be used " + "instead of the double ones)\n", NULL); + } + if ( (s=strusage(19)) ) + { + writestrings (0, "\n", NULL); + writestrings (0, s, NULL); + } + flushstrings (0); + exit(0); +} + +static void +show_version () +{ + const char *s; + int i; + + /* Version line. */ + writestrings (0, strusage (11), NULL); + if ((s=strusage (12))) + writestrings (0, " (", s, ")", NULL); + writestrings (0, " ", strusage (13), "\n", NULL); + /* Additional version lines. */ + for (i=20; i < 30; i++) + if ((s=strusage (i))) + writestrings (0, s, "\n", NULL); + /* Copyright string. */ + if ((s=strusage (14))) + writestrings (0, s, "\n", NULL); + /* Licence string. */ + if( (s=strusage (10)) ) + writestrings (0, s, "\n", NULL); + /* Copying conditions. */ + if ( (s=strusage(15)) ) + writestrings (0, s, NULL); + /* Thanks. */ + if ((s=strusage(18))) + writestrings (0, s, NULL); + /* Additional program info. */ + for (i=30; i < 40; i++ ) + if ( (s=strusage (i)) ) + writestrings (0, s, NULL); + flushstrings (0); +} + + +void +usage (int level) +{ + const char *p; + + if (!level) + { + writestrings (1, strusage(11), " ", strusage(13), "; ", + strusage (14), "\n", NULL); + flushstrings (1); + } + else if (level == 1) + { + p = strusage (40); + writestrings (1, p, NULL); + if (*p && p[strlen(p)] != '\n') + writestrings (1, "\n", NULL); + exit (2); + } + else if (level == 2) + { + p = strusage (42); + if (p && *p == '1') + { + p = strusage (40); + writestrings (1, p, NULL); + if (*p && p[strlen(p)] != '\n') + writestrings (1, "\n", NULL); + } + writestrings (0, strusage(41), "\n", NULL); + exit (0); + } +} + +/* Level + * 0: Print copyright string to stderr + * 1: Print a short usage hint to stderr and terminate + * 2: Print a long usage hint to stdout and terminate + * 10: Return license info string + * 11: Return the name of the program + * 12: Return optional name of package which includes this program. + * 13: version string + * 14: copyright string + * 15: Short copying conditions (with LFs) + * 16: Long copying conditions (with LFs) + * 17: Optional printable OS name + * 18: Optional thanks list (with LFs) + * 19: Bug report info + *20..29: Additional lib version strings. + *30..39: Additional program info (with LFs) + * 40: short usage note (with LF) + * 41: long usage note (with LF) + * 42: Flag string: + * First char is '1': + * The short usage notes needs to be printed + * before the long usage note. + */ +const char * +strusage( int level ) +{ + const char *p = strusage_handler? strusage_handler(level) : NULL; + + if ( p ) + return map_static_macro_string (p); + + switch ( level ) + { + + case 10: +#if ARGPARSE_GPL_VERSION == 3 + p = ("License GPLv3+: GNU GPL version 3 or later " + ""); +#else + p = ("License GPLv2+: GNU GPL version 2 or later " + ""); +#endif + break; + case 11: p = "foo"; break; + case 13: p = "0.0"; break; + case 14: p = ARGPARSE_CRIGHT_STR; break; + case 15: p = +"This is free software: you are free to change and redistribute it.\n" +"There is NO WARRANTY, to the extent permitted by law.\n"; + break; + case 16: p = +"This is free software; you can redistribute it and/or modify\n" +"it under the terms of the GNU General Public License as published by\n" +"the Free Software Foundation; either version " +ARGPARSE_STR2(ARGPARSE_GPL_VERSION) +" of the License, or\n" +"(at your option) any later version.\n\n" +"It is distributed in the hope that it will be useful,\n" +"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +"GNU General Public License for more details.\n\n" +"You should have received a copy of the GNU General Public License\n" +"along with this software. If not, see .\n"; + break; + case 40: /* short and long usage */ + case 41: p = ""; break; + } + + return p; +} + + +/* Set the usage handler. This function is basically a constructor. */ +void +set_strusage ( const char *(*f)( int ) ) +{ + strusage_handler = f; +} + + +#ifdef TEST +static struct { + int verbose; + int debug; + char *outfile; + char *crf; + int myopt; + int echo; + int a_long_one; +} opt; + +int +main(int argc, char **argv) +{ + ARGPARSE_OPTS opts[] = { + ARGPARSE_x('v', "verbose", NONE, 0, "Laut sein"), + ARGPARSE_s_n('e', "echo" , ("Zeile ausgeben, damit wir sehen, " + "was wir eingegeben haben")), + ARGPARSE_s_n('d', "debug", "Debug\nfalls mal etwas\nschief geht"), + ARGPARSE_s_s('o', "output", 0 ), + ARGPARSE_o_s('c', "cross-ref", "cross-reference erzeugen\n" ), + /* Note that on a non-utf8 terminal the ß might garble the output. */ + ARGPARSE_s_n('s', "street","|Straße|set the name of the street to Straße"), + ARGPARSE_o_i('m', "my-option", 0), + ARGPARSE_s_n(500, "a-long-option", 0 ), + ARGPARSE_end() + }; + ARGPARSE_ARGS pargs = { &argc, &argv, (ARGPARSE_FLAG_ALL + | ARGPARSE_FLAG_MIXED + | ARGPARSE_FLAG_ONEDASH) }; + int i; + + while (arg_parse (&pargs, opts)) + { + switch (pargs.r_opt) + { + case ARGPARSE_IS_ARG : + printf ("arg='%s'\n", pargs.r.ret_str); + break; + case 'v': opt.verbose++; break; + case 'e': opt.echo++; break; + case 'd': opt.debug++; break; + case 'o': opt.outfile = pargs.r.ret_str; break; + case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; + case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; + case 500: opt.a_long_one++; break; + default : pargs.err = ARGPARSE_PRINT_WARNING; break; + } + } + for (i=0; i < argc; i++ ) + printf ("%3d -> (%s)\n", i, argv[i] ); + puts ("Options:"); + if (opt.verbose) + printf (" verbose=%d\n", opt.verbose ); + if (opt.debug) + printf (" debug=%d\n", opt.debug ); + if (opt.outfile) + printf (" outfile='%s'\n", opt.outfile ); + if (opt.crf) + printf (" crffile='%s'\n", opt.crf ); + if (opt.myopt) + printf (" myopt=%d\n", opt.myopt ); + if (opt.a_long_one) + printf (" a-long-one=%d\n", opt.a_long_one ); + if (opt.echo) + printf (" echo=%d\n", opt.echo ); + + return 0; +} +#endif /*TEST*/ + +/**** bottom of file ****/ diff --git a/pinentry/argparse.h b/pinentry/argparse.h new file mode 100644 index 0000000..b4dc253 --- /dev/null +++ b/pinentry/argparse.h @@ -0,0 +1,203 @@ +/* argparse.h - Argument parser for option handling. + * Copyright (C) 1998,1999,2000,2001,2006 Free Software Foundation, Inc. + * + * This file is part of JNLIB, which is a subsystem of GnuPG. + * + * JNLIB is free software; you can redistribute it and/or modify it + * under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * or both in parallel, as here. + * + * JNLIB is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copies of the GNU General Public License + * and the GNU Lesser General Public License along with this program; + * if not, see . + */ + +#ifndef LIBJNLIB_ARGPARSE_H +#define LIBJNLIB_ARGPARSE_H + +#include + +typedef struct +{ + int *argc; /* Pointer to ARGC (value subject to change). */ + char ***argv; /* Pointer to ARGV (value subject to change). */ + unsigned int flags; /* Global flags. May be set prior to calling the + parser. The parser may change the value. */ + int err; /* Print error description for last option. + Either 0, ARGPARSE_PRINT_WARNING or + ARGPARSE_PRINT_ERROR. */ + + int r_opt; /* Returns option code. */ + int r_type; /* Returns type of option value. */ + union { + int ret_int; + long ret_long; + unsigned long ret_ulong; + char *ret_str; + } r; /* Return values */ + + struct { + int idx; + int inarg; + int stopped; + const char *last; + void *aliases; + const void *cur_alias; + void *iio_list; + } internal; /* Private - do not change. */ +} ARGPARSE_ARGS; + +typedef struct +{ + int short_opt; + const char *long_opt; + unsigned int flags; + const char *description; /* Optional option description. */ +} ARGPARSE_OPTS; + + +/* Global flags (ARGPARSE_ARGS). */ +#define ARGPARSE_FLAG_KEEP 1 /* Do not remove options form argv. */ +#define ARGPARSE_FLAG_ALL 2 /* Do not stop at last option but return + remaining args with R_OPT set to -1. */ +#define ARGPARSE_FLAG_MIXED 4 /* Assume options and args are mixed. */ +#define ARGPARSE_FLAG_NOSTOP 8 /* Do not stop processing at "--". */ +#define ARGPARSE_FLAG_ARG0 16 /* Do not skip the first arg. */ +#define ARGPARSE_FLAG_ONEDASH 32 /* Allow long options with one dash. */ +#define ARGPARSE_FLAG_NOVERSION 64 /* No output for "--version". */ + +#define ARGPARSE_FLAG_STOP_SEEN 256 /* Set to true if a "--" has been seen. */ + +/* Flags for each option (ARGPARSE_OPTS). The type code may be + ORed with the OPT flags. */ +#define ARGPARSE_TYPE_NONE 0 /* Does not take an argument. */ +#define ARGPARSE_TYPE_INT 1 /* Takes an int argument. */ +#define ARGPARSE_TYPE_STRING 2 /* Takes a string argument. */ +#define ARGPARSE_TYPE_LONG 3 /* Takes a long argument. */ +#define ARGPARSE_TYPE_ULONG 4 /* Takes an unsigned long argument. */ +#define ARGPARSE_OPT_OPTIONAL (1<<3) /* Argument is optional. */ +#define ARGPARSE_OPT_PREFIX (1<<4) /* Allow 0x etc. prefixed values. */ +#define ARGPARSE_OPT_IGNORE (1<<6) /* Ignore command or option. */ +#define ARGPARSE_OPT_COMMAND (1<<7) /* The argument is a command. */ + +#define ARGPARSE_TYPE_MASK 7 /* Mask for the type values (internal). */ + +/* A set of macros to make option definitions easier to read. */ +#define ARGPARSE_x(s,l,t,f,d) \ + { (s), (l), ARGPARSE_TYPE_ ## t | (f), (d) } + +#define ARGPARSE_s(s,l,t,d) \ + { (s), (l), ARGPARSE_TYPE_ ## t, (d) } +#define ARGPARSE_s_n(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_NONE, (d) } +#define ARGPARSE_s_i(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_INT, (d) } +#define ARGPARSE_s_s(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_STRING, (d) } +#define ARGPARSE_s_l(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_LONG, (d) } +#define ARGPARSE_s_u(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_ULONG, (d) } + +#define ARGPARSE_o(s,l,t,d) \ + { (s), (l), (ARGPARSE_TYPE_ ## t | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_n(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_NONE | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_i(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_INT | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_s(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_STRING | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_l(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_LONG | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_u(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_ULONG | ARGPARSE_OPT_OPTIONAL), (d) } + +#define ARGPARSE_p(s,l,t,d) \ + { (s), (l), (ARGPARSE_TYPE_ ## t | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_n(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_NONE | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_i(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_INT | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_s(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_STRING | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_l(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_LONG | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_u(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_ULONG | ARGPARSE_OPT_PREFIX), (d) } + +#define ARGPARSE_op(s,l,t,d) \ + { (s), (l), (ARGPARSE_TYPE_ ## t \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_n(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_NONE \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_i(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_INT \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_s(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_STRING \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_l(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_LONG \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_u(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_ULONG \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } + +#define ARGPARSE_c(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_NONE | ARGPARSE_OPT_COMMAND), (d) } + +#define ARGPARSE_ignore(s,l) \ + { (s), (l), (ARGPARSE_OPT_IGNORE), "@" } + +#define ARGPARSE_group(s,d) \ + { (s), NULL, 0, (d) } + +#define ARGPARSE_end() { 0, NULL, 0, NULL } + + +/* Other constants. */ +#define ARGPARSE_PRINT_WARNING 1 +#define ARGPARSE_PRINT_ERROR 2 + + +/* Error values. */ +#define ARGPARSE_IS_ARG (-1) +#define ARGPARSE_INVALID_OPTION (-2) +#define ARGPARSE_MISSING_ARG (-3) +#define ARGPARSE_KEYWORD_TOO_LONG (-4) +#define ARGPARSE_READ_ERROR (-5) +#define ARGPARSE_UNEXPECTED_ARG (-6) +#define ARGPARSE_INVALID_COMMAND (-7) +#define ARGPARSE_AMBIGUOUS_OPTION (-8) +#define ARGPARSE_AMBIGUOUS_COMMAND (-9) +#define ARGPARSE_INVALID_ALIAS (-10) +#define ARGPARSE_OUT_OF_CORE (-11) +#define ARGPARSE_INVALID_ARG (-12) + + +int arg_parse (ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts); +int optfile_parse (FILE *fp, const char *filename, unsigned *lineno, + ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts); +void usage (int level); +const char *strusage (int level); +void set_strusage (const char *(*f)( int )); +void argparse_register_outfnc (int (*fnc)(int, const char *)); + +#endif /*LIBJNLIB_ARGPARSE_H*/ diff --git a/pinentry/memory.h b/pinentry/memory.h new file mode 100644 index 0000000..354c6c9 --- /dev/null +++ b/pinentry/memory.h @@ -0,0 +1,55 @@ +/* Quintuple Agent secure memory allocation + * Copyright (C) 1998,1999 Free Software Foundation, Inc. + * Copyright (C) 1999,2000 Robert Bihlmeyer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _MEMORY_H +#define _MEMORY_H + +#include + +#ifdef __cplusplus +extern "C" { +#if 0 +} +#endif +#endif + + +/* values for flags, hardcoded in secmem.c */ +#define SECMEM_WARN 0 +#define SECMEM_DONT_WARN 1 +#define SECMEM_SUSPEND_WARN 2 + +void secmem_init( size_t npool ); +void secmem_term( void ); +void *secmem_malloc( size_t size ); +void *secmem_realloc( void *a, size_t newsize ); +void secmem_free( void *a ); +int m_is_secure( const void *p ); +void secmem_dump_stats(void); +void secmem_set_flags( unsigned flags ); +unsigned secmem_get_flags(void); +size_t secmem_get_max_size (void); + +#if 0 +{ +#endif +#ifdef __cplusplus +} +#endif +#endif /* _MEMORY_H */ diff --git a/pinentry/password-cache.c b/pinentry/password-cache.c new file mode 100644 index 0000000..70b33f4 --- /dev/null +++ b/pinentry/password-cache.c @@ -0,0 +1,163 @@ +/* password-cache.c - Password cache support. + Copyright (C) 2015 g10 Code GmbH + + This file is part of PINENTRY. + + PINENTRY is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PINENTRY is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#ifdef HAVE_LIBSECRET +# include +#endif + +#include "password-cache.h" +#include "memory.h" + +#ifdef HAVE_LIBSECRET +static const SecretSchema * +gpg_schema (void) +{ + static const SecretSchema the_schema = { + "org.gnupg.Passphrase", SECRET_SCHEMA_NONE, + { + { "stored-by", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "keygrip", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "NULL", 0 }, + } + }; + return &the_schema; +} + +static char * +keygrip_to_label (const char *keygrip) +{ + char const prefix[] = "GnuPG: "; + char *label; + + label = malloc (sizeof (prefix) + strlen (keygrip)); + if (label) + { + memcpy (label, prefix, sizeof (prefix) - 1); + strcpy (&label[sizeof (prefix) - 1], keygrip); + } + return label; +} +#endif + +void +password_cache_save (const char *keygrip, const char *password) +{ +#ifdef HAVE_LIBSECRET + char *label; + GError *error = NULL; + + if (! *keygrip) + return; + + label = keygrip_to_label (keygrip); + if (! label) + return; + + if (! secret_password_store_sync (gpg_schema (), + SECRET_COLLECTION_DEFAULT, + label, password, NULL, &error, + "stored-by", "GnuPG Pinentry", + "keygrip", keygrip, NULL)) + { + printf("Failed to cache password for key %s with secret service: %s\n", + keygrip, error->message); + + g_error_free (error); + } + + free (label); +#else + return; +#endif +} + +char * +password_cache_lookup (const char *keygrip) +{ +#ifdef HAVE_LIBSECRET + GError *error = NULL; + char *password; + char *password2; + + if (! *keygrip) + return NULL; + + password = secret_password_lookup_nonpageable_sync + (gpg_schema (), NULL, &error, + "keygrip", keygrip, NULL); + + if (error != NULL) + { + printf("Failed to lookup password for key %s with secret service: %s\n", + keygrip, error->message); + g_error_free (error); + return NULL; + } + if (! password) + /* The password for this key is not cached. Just return NULL. */ + return NULL; + + /* The password needs to be returned in secmem allocated memory. */ + password2 = secmem_malloc (strlen (password) + 1); + if (password2) + strcpy(password2, password); + else + printf("secmem_malloc failed: can't copy password!\n"); + + secret_password_free (password); + + return password2; +#else + return NULL; +#endif +} + +/* Try and remove the cached password for key grip. Returns -1 on + error, 0 if the key is not found and 1 if the password was + removed. */ +int +password_cache_clear (const char *keygrip) +{ +#ifdef HAVE_LIBSECRET + GError *error = NULL; + int removed = secret_password_clear_sync (gpg_schema (), NULL, &error, + "keygrip", keygrip, NULL); + if (error != NULL) + { + printf("Failed to clear password for key %s with secret service: %s\n", + keygrip, error->message); + g_debug("%s", error->message); + g_error_free (error); + return -1; + } + if (removed) + return 1; + return 0; +#else + return -1; +#endif +} diff --git a/pinentry/password-cache.h b/pinentry/password-cache.h new file mode 100644 index 0000000..0bc8788 --- /dev/null +++ b/pinentry/password-cache.h @@ -0,0 +1,29 @@ +/* password-cache.h - Password cache support interfaces. + Copyright (C) 2015 g10 Code GmbH + + This file is part of PINENTRY. + + PINENTRY is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PINENTRY is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . + */ + +#ifndef PASSWORD_CACHE_H +#define PASSWORD_CACHE_H + +void password_cache_save (const char *key_grip, const char *password); + +char *password_cache_lookup (const char *key_grip); + +int password_cache_clear (const char *keygrip); + +#endif diff --git a/pinentry/pinentry.c b/pinentry/pinentry.c new file mode 100644 index 0000000..f305a7d --- /dev/null +++ b/pinentry/pinentry.c @@ -0,0 +1,1308 @@ +/* pinentry.c - The PIN entry support library + Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015 g10 Code GmbH + + This file is part of PINENTRY. + + PINENTRY is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PINENTRY is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include + +#include "memory.h" +#include "secmem-util.h" +#include "argparse.h" +#include "pinentry.h" +#include "password-cache.h" + +/* Keep the name of our program here. */ +static char this_pgmname[50]; + +struct pinentry pinentry; + +static void +pinentry_reset (int use_defaults) +{ + /* GPG Agent sets these options once when it starts the pinentry. + Don't reset them. */ + int grab = pinentry.grab; + char *ttyname = pinentry.ttyname; + char *ttytype = pinentry.ttytype; + char *lc_ctype = pinentry.lc_ctype; + char *lc_messages = pinentry.lc_messages; + int allow_external_password_cache = pinentry.allow_external_password_cache; + char *default_ok = pinentry.default_ok; + char *default_cancel = pinentry.default_cancel; + char *default_prompt = pinentry.default_prompt; + char *default_pwmngr = pinentry.default_pwmngr; + char *touch_file = pinentry.touch_file; + + /* These options are set from the command line. Don't reset + them. */ + int debug = pinentry.debug; + char *display = pinentry.display; + int parent_wid = pinentry.parent_wid; + + pinentry_color_t color_fg = pinentry.color_fg; + int color_fg_bright = pinentry.color_fg_bright; + pinentry_color_t color_bg = pinentry.color_bg; + pinentry_color_t color_so = pinentry.color_so; + int color_so_bright = pinentry.color_so_bright; + + int timout = pinentry.timeout; + + /* Free any allocated memory. */ + if (use_defaults) + { + free (pinentry.ttyname); + free (pinentry.ttytype); + free (pinentry.lc_ctype); + free (pinentry.lc_messages); + free (pinentry.default_ok); + free (pinentry.default_cancel); + free (pinentry.default_prompt); + free (pinentry.default_pwmngr); + free (pinentry.touch_file); + free (pinentry.display); + } + + free (pinentry.title); + free (pinentry.description); + free (pinentry.error); + free (pinentry.prompt); + free (pinentry.ok); + free (pinentry.notok); + free (pinentry.cancel); + secmem_free (pinentry.pin); + free (pinentry.repeat_passphrase); + free (pinentry.repeat_error_string); + free (pinentry.quality_bar); + free (pinentry.quality_bar_tt); + free (pinentry.keyinfo); + + /* Reset the pinentry structure. */ + memset (&pinentry, 0, sizeof (pinentry)); + + if (use_defaults) + { + /* Pinentry timeout in seconds. */ + pinentry.timeout = 60; + + /* Global grab. */ + pinentry.grab = 1; + + pinentry.color_fg = PINENTRY_COLOR_DEFAULT; + pinentry.color_fg_bright = 0; + pinentry.color_bg = PINENTRY_COLOR_DEFAULT; + pinentry.color_so = PINENTRY_COLOR_DEFAULT; + pinentry.color_so_bright = 0; + } + else + /* Restore the options. */ + { + pinentry.grab = grab; + pinentry.ttyname = ttyname; + pinentry.ttytype = ttytype; + pinentry.lc_ctype = lc_ctype; + pinentry.lc_messages = lc_messages; + pinentry.allow_external_password_cache = allow_external_password_cache; + pinentry.default_ok = default_ok; + pinentry.default_cancel = default_cancel; + pinentry.default_prompt = default_prompt; + pinentry.default_pwmngr = default_pwmngr; + pinentry.touch_file = touch_file; + + pinentry.debug = debug; + pinentry.display = display; + pinentry.parent_wid = parent_wid; + + pinentry.color_fg = color_fg; + pinentry.color_fg_bright = color_fg_bright; + pinentry.color_bg = color_bg; + pinentry.color_so = color_so; + pinentry.color_so_bright = color_so_bright; + + pinentry.timeout = timout; + } +} + +static gpg_error_t +pinentry_assuan_reset_handler (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + + pinentry_reset (0); + + return 0; +} + + + +static int lc_ctype_unknown_warning = 0; + +/* Copy TEXT or TEXTLEN to BUFFER and escape as required. Return a + pointer to the end of the new buffer. Note that BUFFER must be + large enough to keep the entire text; allocataing it 3 times of + TEXTLEN is sufficient. */ +static char * +copy_and_escape (char *buffer, const void *text, size_t textlen) +{ + int i; + const unsigned char *s = (unsigned char *)text; + char *p = buffer; + + for (i=0; i < textlen; i++) + { + if (s[i] < ' ' || s[i] == '+') + { + snprintf (p, 4, "%%%02X", s[i]); + p += 3; + } + else if (s[i] == ' ') + *p++ = '+'; + else + *p++ = s[i]; + } + return p; +} + + + +/* Run a quality inquiry for PASSPHRASE of LENGTH. (We need LENGTH + because not all backends might be able to return a proper + C-string.). Returns: A value between -100 and 100 to give an + estimate of the passphrase's quality. Negative values are use if + the caller won't even accept that passphrase. Note that we expect + just one data line which should not be escaped in any represent a + numeric signed decimal value. Extra data is currently ignored but + should not be send at all. */ +int +pinentry_inq_quality (pinentry_t pin, const char *passphrase, size_t length) +{ + assuan_context_t ctx = pin->ctx_assuan; + const char prefix[] = "INQUIRE QUALITY "; + char *command; + char *line; + size_t linelen; + int gotvalue = 0; + int value = 0; + int rc; + + if (!ctx) + return 0; /* Can't run the callback. */ + + if (length > 300) + length = 300; /* Limit so that it definitely fits into an Assuan + line. */ + + command = secmem_malloc (strlen (prefix) + 3*length + 1); + if (!command) + return 0; + strcpy (command, prefix); + copy_and_escape (command + strlen(command), passphrase, length); + rc = assuan_write_line (ctx, command); + secmem_free (command); + if (rc) + { + fprintf (stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc); + return 0; + } + + for (;;) + { + do + { + rc = assuan_read_line (ctx, &line, &linelen); + if (rc) + { + fprintf (stderr, "ASSUAN READ LINE failed: rc=%d\n", rc); + return 0; + } + } + while (*line == '#' || !linelen); + if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' + && (!line[3] || line[3] == ' ')) + break; /* END command received*/ + if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' + && (!line[3] || line[3] == ' ')) + break; /* CAN command received*/ + if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' + && (!line[3] || line[3] == ' ')) + break; /* ERR command received*/ + if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) + continue; + gotvalue = 1; + value = atoi (line+2); + } + if (value < -100) + value = -100; + else if (value > 100) + value = 100; + + return value; +} + + + +/* Try to make room for at least LEN bytes in the pinentry. Returns + new buffer on success and 0 on failure or when the old buffer is + sufficient. */ +char * +pinentry_setbufferlen (pinentry_t pin, int len) +{ + char *newp; + + if (pin->pin_len) + assert (pin->pin); + else + assert (!pin->pin); + + if (len < 2048) + len = 2048; + + if (len <= pin->pin_len) + return pin->pin; + + newp = secmem_realloc (pin->pin, len); + if (newp) + { + pin->pin = newp; + pin->pin_len = len; + } + else + { + secmem_free (pin->pin); + pin->pin = 0; + pin->pin_len = 0; + } + return newp; +} + +static void +pinentry_setbuffer_clear (pinentry_t pin) +{ + if (! pin->pin) + { + assert (pin->pin_len == 0); + return; + } + + assert (pin->pin_len > 0); + + secmem_free (pin->pin); + pin->pin = NULL; + pin->pin_len = 0; +} + +static void +pinentry_setbuffer_init (pinentry_t pin) +{ + pinentry_setbuffer_clear (pin); + pinentry_setbufferlen (pin, 0); +} + +/* passphrase better be alloced with secmem_alloc. */ +void +pinentry_setbuffer_use (pinentry_t pin, char *passphrase, int len) +{ + if (! passphrase) + { + assert (len == 0); + pinentry_setbuffer_clear (pin); + + return; + } + + if (passphrase && len == 0) + len = strlen (passphrase) + 1; + + if (pin->pin) + secmem_free (pin->pin); + + pin->pin = passphrase; + pin->pin_len = len; +} + +static struct assuan_malloc_hooks assuan_malloc_hooks = { + secmem_malloc, secmem_realloc, secmem_free +}; + +/* Initialize the secure memory subsystem, drop privileges and return. + Must be called early. */ +void +pinentry_init (const char *pgmname) +{ + /* Store away our name. */ + if (strlen (pgmname) > sizeof this_pgmname - 2) + abort (); + strcpy (this_pgmname, pgmname); + + gpgrt_check_version (NULL); + + /* Initialize secure memory. 1 is too small, so the default size + will be used. */ + secmem_init (1); + secmem_set_flags (SECMEM_WARN); + drop_privs (); + + if (atexit (secmem_term)) + { + /* FIXME: Could not register at-exit function, bail out. */ + } + + assuan_set_malloc_hooks (&assuan_malloc_hooks); +} + +/* Simple test to check whether DISPLAY is set or the option --display + was given. Used to decide whether the GUI or curses should be + initialized. */ +int +pinentry_have_display (int argc, char **argv) +{ + for (; argc; argc--, argv++) + if (!strcmp (*argv, "--display") || !strncmp (*argv, "--display=", 10)) + return 1; + return 0; +} + + + +/* Print usage information and and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 11: p = this_pgmname; break; + case 12: p = "pinentry"; break; + case 13: p = PACKAGE_VERSION; break; + case 14: p = "Copyright (C) 2015 g10 Code GmbH"; break; + case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break; + case 1: + case 40: + { + static char *str; + + if (!str) + { + size_t n = 50 + strlen (this_pgmname); + str = malloc (n); + if (str) + snprintf (str, n, "Usage: %s [options] (-h for help)", + this_pgmname); + } + p = str; + } + break; + case 41: + p = "Ask securely for a secret and print it to stdout."; + break; + + case 42: + p = "1"; /* Flag print 40 as part of 41. */ + break; + + default: p = NULL; break; + } + return p; +} + + +char * +parse_color (char *arg, pinentry_color_t *color_p, int *bright_p) +{ + static struct + { + const char *name; + pinentry_color_t color; + } colors[] = { { "none", PINENTRY_COLOR_NONE }, + { "default", PINENTRY_COLOR_DEFAULT }, + { "black", PINENTRY_COLOR_BLACK }, + { "red", PINENTRY_COLOR_RED }, + { "green", PINENTRY_COLOR_GREEN }, + { "yellow", PINENTRY_COLOR_YELLOW }, + { "blue", PINENTRY_COLOR_BLUE }, + { "magenta", PINENTRY_COLOR_MAGENTA }, + { "cyan", PINENTRY_COLOR_CYAN }, + { "white", PINENTRY_COLOR_WHITE } }; + + int i; + char *new_arg; + pinentry_color_t color = PINENTRY_COLOR_DEFAULT; + + if (!arg) + return NULL; + + new_arg = strchr (arg, ','); + if (new_arg) + new_arg++; + + if (bright_p) + { + const char *bname[] = { "bright-", "bright", "bold-", "bold" }; + + *bright_p = 0; + for (i = 0; i < sizeof (bname) / sizeof (bname[0]); i++) + if (!strncasecmp (arg, bname[i], strlen (bname[i]))) + { + *bright_p = 1; + arg += strlen (bname[i]); + } + } + + for (i = 0; i < sizeof (colors) / sizeof (colors[0]); i++) + if (!strncasecmp (arg, colors[i].name, strlen (colors[i].name))) + color = colors[i].color; + + *color_p = color; + return new_arg; +} + +/* Parse the command line options. May exit the program if only help + or version output is requested. */ +void +pinentry_parse_opts (int argc, char *argv[]) +{ + static ARGPARSE_OPTS opts[] = { + ARGPARSE_s_n('d', "debug", "Turn on debugging output"), + ARGPARSE_s_s('D', "display", "|DISPLAY|Set the X display"), + ARGPARSE_s_s('T', "ttyname", "|FILE|Set the tty terminal node name"), + ARGPARSE_s_s('N', "ttytype", "|NAME|Set the tty terminal type"), + ARGPARSE_s_s('C', "lc-ctype", "|STRING|Set the tty LC_CTYPE value"), + ARGPARSE_s_s('M', "lc-messages", "|STRING|Set the tty LC_MESSAGES value"), + ARGPARSE_s_i('o', "timeout", + "|SECS|Timeout waiting for input after this many seconds"), + ARGPARSE_s_n('g', "no-global-grab", + "Grab keyboard only while window is focused"), + ARGPARSE_s_u('W', "parent-wid", "Parent window ID (for positioning)"), + ARGPARSE_s_s('c', "colors", "|STRING|Set custom colors for ncurses"), + ARGPARSE_end() + }; + ARGPARSE_ARGS pargs = { &argc, &argv, 0 }; + + set_strusage (my_strusage); + + pinentry_reset (1); + + while (arg_parse (&pargs, opts)) + { + switch (pargs.r_opt) + { + case 'd': + pinentry.debug = 1; + break; + case 'g': + pinentry.grab = 0; + break; + + case 'D': + /* Note, this is currently not used because the GUI engine + has already been initialized when parsing these options. */ + pinentry.display = strdup (pargs.r.ret_str); + if (!pinentry.display) + { + exit (EXIT_FAILURE); + } + break; + case 'T': + pinentry.ttyname = strdup (pargs.r.ret_str); + if (!pinentry.ttyname) + { + exit (EXIT_FAILURE); + } + break; + case 'N': + pinentry.ttytype = strdup (pargs.r.ret_str); + if (!pinentry.ttytype) + { + exit (EXIT_FAILURE); + } + break; + case 'C': + pinentry.lc_ctype = strdup (pargs.r.ret_str); + if (!pinentry.lc_ctype) + { + exit (EXIT_FAILURE); + } + break; + case 'M': + pinentry.lc_messages = strdup (pargs.r.ret_str); + if (!pinentry.lc_messages) + { + exit (EXIT_FAILURE); + } + break; + case 'W': + pinentry.parent_wid = pargs.r.ret_ulong; + break; + + case 'c': + { + char *tmpstr = pargs.r.ret_str; + + tmpstr = parse_color (tmpstr, &pinentry.color_fg, + &pinentry.color_fg_bright); + tmpstr = parse_color (tmpstr, &pinentry.color_bg, NULL); + tmpstr = parse_color (tmpstr, &pinentry.color_so, + &pinentry.color_so_bright); + } + break; + + case 'o': + pinentry.timeout = pargs.r.ret_int; + break; + + default: + pargs.err = ARGPARSE_PRINT_WARNING; + break; + } + } +} + + +static gpg_error_t +option_handler (assuan_context_t ctx, const char *key, const char *value) +{ + (void)ctx; + + if (!strcmp (key, "no-grab") && !*value) + pinentry.grab = 0; + else if (!strcmp (key, "grab") && !*value) + pinentry.grab = 1; + else if (!strcmp (key, "debug-wait")) + { + } + else if (!strcmp (key, "display")) + { + if (pinentry.display) + free (pinentry.display); + pinentry.display = strdup (value); + if (!pinentry.display) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "ttyname")) + { + if (pinentry.ttyname) + free (pinentry.ttyname); + pinentry.ttyname = strdup (value); + if (!pinentry.ttyname) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "ttytype")) + { + if (pinentry.ttytype) + free (pinentry.ttytype); + pinentry.ttytype = strdup (value); + if (!pinentry.ttytype) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "lc-ctype")) + { + if (pinentry.lc_ctype) + free (pinentry.lc_ctype); + pinentry.lc_ctype = strdup (value); + if (!pinentry.lc_ctype) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "lc-messages")) + { + if (pinentry.lc_messages) + free (pinentry.lc_messages); + pinentry.lc_messages = strdup (value); + if (!pinentry.lc_messages) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "parent-wid")) + { + pinentry.parent_wid = atoi (value); + /* FIXME: Use strtol and add some error handling. */ + } + else if (!strcmp (key, "touch-file")) + { + if (pinentry.touch_file) + free (pinentry.touch_file); + pinentry.touch_file = strdup (value); + if (!pinentry.touch_file) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "default-ok")) + { + pinentry.default_ok = strdup (value); + if (!pinentry.default_ok) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "default-cancel")) + { + pinentry.default_cancel = strdup (value); + if (!pinentry.default_cancel) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "default-prompt")) + { + pinentry.default_prompt = strdup (value); + if (!pinentry.default_prompt) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "default-pwmngr")) + { + pinentry.default_pwmngr = strdup (value); + if (!pinentry.default_pwmngr) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "allow-external-password-cache") && !*value) + { + pinentry.allow_external_password_cache = 1; + pinentry.tried_password_cache = 0; + } + else if (!strcmp (key, "allow-emacs-prompt") && !*value) + { + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else + return gpg_error (GPG_ERR_UNKNOWN_OPTION); + return 0; +} + + +/* Note, that it is sufficient to allocate the target string D as + long as the source string S, i.e.: strlen(s)+1; */ +static void +strcpy_escaped (char *d, const char *s) +{ + while (*s) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d++ = xtoi_2 ( s); + s += 2; + } + else + *d++ = *s++; + } + *d = 0; +} + + +static gpg_error_t +cmd_setdesc (assuan_context_t ctx, char *line) +{ + char *newd; + + (void)ctx; + + newd = malloc (strlen (line) + 1); + if (!newd) + return gpg_error_from_syserror (); + + strcpy_escaped (newd, line); + if (pinentry.description) + free (pinentry.description); + pinentry.description = newd; + return 0; +} + + +static gpg_error_t +cmd_setprompt (assuan_context_t ctx, char *line) +{ + char *newp; + + (void)ctx; + + newp = malloc (strlen (line) + 1); + if (!newp) + return gpg_error_from_syserror (); + + strcpy_escaped (newp, line); + if (pinentry.prompt) + free (pinentry.prompt); + pinentry.prompt = newp; + return 0; +} + + +/* The data provided at LINE may be used by pinentry implementations + to identify a key for caching strategies of its own. The empty + string and --clear mean that the key does not have a stable + identifier. */ +static gpg_error_t +cmd_setkeyinfo (assuan_context_t ctx, char *line) +{ + (void)ctx; + + if (pinentry.keyinfo) + free (pinentry.keyinfo); + + if (*line && strcmp(line, "--clear") != 0) + pinentry.keyinfo = strdup (line); + else + pinentry.keyinfo = NULL; + + return 0; +} + + +static gpg_error_t +cmd_setrepeat (assuan_context_t ctx, char *line) +{ + char *p; + + (void)ctx; + + p = malloc (strlen (line) + 1); + if (!p) + return gpg_error_from_syserror (); + + strcpy_escaped (p, line); + free (pinentry.repeat_passphrase); + pinentry.repeat_passphrase = p; + return 0; +} + + +static gpg_error_t +cmd_setrepeaterror (assuan_context_t ctx, char *line) +{ + char *p; + + (void)ctx; + + p = malloc (strlen (line) + 1); + if (!p) + return gpg_error_from_syserror (); + + strcpy_escaped (p, line); + free (pinentry.repeat_error_string); + pinentry.repeat_error_string = p; + return 0; +} + + +static gpg_error_t +cmd_seterror (assuan_context_t ctx, char *line) +{ + char *newe; + + (void)ctx; + + newe = malloc (strlen (line) + 1); + if (!newe) + return gpg_error_from_syserror (); + + strcpy_escaped (newe, line); + if (pinentry.error) + free (pinentry.error); + pinentry.error = newe; + return 0; +} + + +static gpg_error_t +cmd_setok (assuan_context_t ctx, char *line) +{ + char *newo; + + (void)ctx; + + newo = malloc (strlen (line) + 1); + if (!newo) + return gpg_error_from_syserror (); + + strcpy_escaped (newo, line); + if (pinentry.ok) + free (pinentry.ok); + pinentry.ok = newo; + return 0; +} + + +static gpg_error_t +cmd_setnotok (assuan_context_t ctx, char *line) +{ + char *newo; + + (void)ctx; + + newo = malloc (strlen (line) + 1); + if (!newo) + return gpg_error_from_syserror (); + + strcpy_escaped (newo, line); + if (pinentry.notok) + free (pinentry.notok); + pinentry.notok = newo; + return 0; +} + + +static gpg_error_t +cmd_setcancel (assuan_context_t ctx, char *line) +{ + char *newc; + + (void)ctx; + + newc = malloc (strlen (line) + 1); + if (!newc) + return gpg_error_from_syserror (); + + strcpy_escaped (newc, line); + if (pinentry.cancel) + free (pinentry.cancel); + pinentry.cancel = newc; + return 0; +} + + +static gpg_error_t +cmd_settimeout (assuan_context_t ctx, char *line) +{ + (void)ctx; + + if (line && *line) + pinentry.timeout = atoi (line); + + return 0; +} + +static gpg_error_t +cmd_settitle (assuan_context_t ctx, char *line) +{ + char *newt; + + (void)ctx; + + newt = malloc (strlen (line) + 1); + if (!newt) + return gpg_error_from_syserror (); + + strcpy_escaped (newt, line); + if (pinentry.title) + free (pinentry.title); + pinentry.title = newt; + return 0; +} + +static gpg_error_t +cmd_setqualitybar (assuan_context_t ctx, char *line) +{ + char *newval; + + (void)ctx; + + if (!*line) + line = "Quality:"; + + newval = malloc (strlen (line) + 1); + if (!newval) + return gpg_error_from_syserror (); + + strcpy_escaped (newval, line); + if (pinentry.quality_bar) + free (pinentry.quality_bar); + pinentry.quality_bar = newval; + return 0; +} + +/* Set the tooltip to be used for a quality bar. */ +static gpg_error_t +cmd_setqualitybar_tt (assuan_context_t ctx, char *line) +{ + char *newval; + + (void)ctx; + + if (*line) + { + newval = malloc (strlen (line) + 1); + if (!newval) + return gpg_error_from_syserror (); + + strcpy_escaped (newval, line); + } + else + newval = NULL; + if (pinentry.quality_bar_tt) + free (pinentry.quality_bar_tt); + pinentry.quality_bar_tt = newval; + return 0; +} + + +static gpg_error_t +cmd_getpin (assuan_context_t ctx, char *line) +{ + int result; + int set_prompt = 0; + int just_read_password_from_cache = 0; + + (void)line; + + pinentry_setbuffer_init (&pinentry); + if (!pinentry.pin) + return gpg_error (GPG_ERR_ENOMEM); + + /* Try reading from the password cache. */ + if (/* If repeat passphrase is set, then we don't want to read from + the cache. */ + ! pinentry.repeat_passphrase + /* Are we allowed to read from the cache? */ + && pinentry.allow_external_password_cache + && pinentry.keyinfo + /* Only read from the cache if we haven't already tried it. */ + && ! pinentry.tried_password_cache + /* If the last read resulted in an error, then don't read from + the cache. */ + && ! pinentry.error) + { + char *password; + + pinentry.tried_password_cache = 1; + + password = password_cache_lookup (pinentry.keyinfo); + if (password) + /* There is a cached password. Try it. */ + { + int len = strlen(password) + 1; + if (len > pinentry.pin_len) + len = pinentry.pin_len; + + memcpy (pinentry.pin, password, len); + pinentry.pin[len] = '\0'; + + secmem_free (password); + + pinentry.pin_from_cache = 1; + + assuan_write_status (ctx, "PASSWORD_FROM_CACHE", ""); + + /* Result is the length of the password not including the + NUL terminator. */ + result = len - 1; + + just_read_password_from_cache = 1; + + goto out; + } + } + + /* The password was not cached (or we are not allowed to / cannot + use the cache). Prompt the user. */ + pinentry.pin_from_cache = 0; + + if (!pinentry.prompt) + { + pinentry.prompt = pinentry.default_prompt?pinentry.default_prompt:"PIN:"; + set_prompt = 1; + } + pinentry.locale_err = 0; + pinentry.specific_err = 0; + pinentry.close_button = 0; + pinentry.repeat_okay = 0; + pinentry.one_button = 0; + pinentry.ctx_assuan = ctx; + result = (*pinentry_cmd_handler) (&pinentry); + pinentry.ctx_assuan = NULL; + if (pinentry.error) + { + free (pinentry.error); + pinentry.error = NULL; + } + if (pinentry.repeat_passphrase) + { + free (pinentry.repeat_passphrase); + pinentry.repeat_passphrase = NULL; + } + if (set_prompt) + pinentry.prompt = NULL; + + pinentry.quality_bar = 0; /* Reset it after the command. */ + + if (pinentry.close_button) + assuan_write_status (ctx, "BUTTON_INFO", "close"); + + if (result < 0) + { + pinentry_setbuffer_clear (&pinentry); + if (pinentry.specific_err) + return pinentry.specific_err; + return (pinentry.locale_err + ? gpg_error (GPG_ERR_LOCALE_PROBLEM) + : gpg_error (GPG_ERR_CANCELED)); + } + + out: + if (result) + { + if (pinentry.repeat_okay) + assuan_write_status (ctx, "PIN_REPEATED", ""); + result = assuan_send_data (ctx, pinentry.pin, strlen(pinentry.pin)); + if (!result) + result = assuan_send_data (ctx, NULL, 0); + + if (/* GPG Agent says it's okay. */ + pinentry.allow_external_password_cache && pinentry.keyinfo + /* We didn't just read it from the cache. */ + && ! just_read_password_from_cache + /* And the user said it's okay. */ + && pinentry.may_cache_password) + /* Cache the password. */ + password_cache_save (pinentry.keyinfo, pinentry.pin); + } + + pinentry_setbuffer_clear (&pinentry); + + return result; +} + + +/* Note that the option --one-button is a hack to allow the use of old + pinentries while the caller is ignoring the result. Given that + options have never been used or flagged as an error the new option + is an easy way to enable the messsage mode while not requiring to + update pinentry or to have the caller test for the message + command. New applications which are free to require an updated + pinentry should use MESSAGE instead. */ +static gpg_error_t +cmd_confirm (assuan_context_t ctx, char *line) +{ + int result; + + pinentry.one_button = !!strstr (line, "--one-button"); + pinentry.quality_bar = 0; + pinentry.close_button = 0; + pinentry.locale_err = 0; + pinentry.specific_err = 0; + pinentry.canceled = 0; + pinentry_setbuffer_clear (&pinentry); + result = (*pinentry_cmd_handler) (&pinentry); + if (pinentry.error) + { + free (pinentry.error); + pinentry.error = NULL; + } + + if (pinentry.close_button) + assuan_write_status (ctx, "BUTTON_INFO", "close"); + + if (result) + return 0; + + if (pinentry.specific_err) + return pinentry.specific_err; + + if (pinentry.locale_err) + return gpg_error (GPG_ERR_LOCALE_PROBLEM); + + if (pinentry.one_button) + return 0; + + if (pinentry.canceled) + return gpg_error (GPG_ERR_CANCELED); + return gpg_error (GPG_ERR_NOT_CONFIRMED); +} + + +static gpg_error_t +cmd_message (assuan_context_t ctx, char *line) +{ + (void)line; + + return cmd_confirm (ctx, "--one-button"); +} + +/* GETINFO + + Multipurpose function to return a variety of information. + Supported values for WHAT are: + + version - Return the version of the program. + pid - Return the process id of the server. + */ +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + int rc; + + if (!strcmp (line, "version")) + { + const char *s = VERSION; + rc = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "pid")) + { + char numbuf[50]; + + snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); + rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else + rc = gpg_error (GPG_ERR_ASS_PARAMETER); + return rc; +} + +/* CLEARPASSPHRASE + + Clear the cache passphrase associated with the key identified by + cacheid. + */ +static gpg_error_t +cmd_clear_passphrase (assuan_context_t ctx, char *line) +{ + (void)ctx; + + if (! line) + return gpg_error (GPG_ERR_ASS_INV_VALUE); + + /* Remove leading and trailing white space. */ + while (*line == ' ') + line ++; + while (line[strlen (line) - 1] == ' ') + line[strlen (line) - 1] = 0; + + switch (password_cache_clear (line)) + { + case 1: return 0; + case 0: return gpg_error (GPG_ERR_ASS_INV_VALUE); + default: return gpg_error (GPG_ERR_ASS_GENERAL); + } +} + +/* Tell the assuan library about our commands. */ +static gpg_error_t +register_commands (assuan_context_t ctx) +{ + static struct + { + const char *name; + gpg_error_t (*handler) (assuan_context_t, char *line); + } table[] = + { + { "SETDESC", cmd_setdesc }, + { "SETPROMPT", cmd_setprompt }, + { "SETKEYINFO", cmd_setkeyinfo }, + { "SETREPEAT", cmd_setrepeat }, + { "SETREPEATERROR", cmd_setrepeaterror }, + { "SETERROR", cmd_seterror }, + { "SETOK", cmd_setok }, + { "SETNOTOK", cmd_setnotok }, + { "SETCANCEL", cmd_setcancel }, + { "GETPIN", cmd_getpin }, + { "CONFIRM", cmd_confirm }, + { "MESSAGE", cmd_message }, + { "SETQUALITYBAR", cmd_setqualitybar }, + { "SETQUALITYBAR_TT", cmd_setqualitybar_tt }, + { "GETINFO", cmd_getinfo }, + { "SETTITLE", cmd_settitle }, + { "SETTIMEOUT", cmd_settimeout }, + { "CLEARPASSPHRASE", cmd_clear_passphrase }, + { NULL } + }; + int i, j; + gpg_error_t rc; + + for (i = j = 0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler, NULL); + if (rc) + return rc; + } + return 0; +} + + +int +pinentry_loop2 (int infd, int outfd) +{ + gpg_error_t rc; + assuan_fd_t filedes[2]; + assuan_context_t ctx; + + /* Extra check to make sure we have dropped privs. */ + if (getuid() != geteuid()) + abort (); + + rc = assuan_new (&ctx); + if (rc) + { + fprintf (stderr, "server context creation failed: %s\n", + gpg_strerror (rc)); + return -1; + } + + /* For now we use a simple pipe based server so that we can work + from scripts. We will later add options to run as a daemon and + wait for requests on a Unix domain socket. */ + filedes[0] = assuan_fdopen (infd); + filedes[1] = assuan_fdopen (outfd); + rc = assuan_init_pipe_server (ctx, filedes); + if (rc) + { + fprintf (stderr, "%s: failed to initialize the server: %s\n", + this_pgmname, gpg_strerror (rc)); + return -1; + } + rc = register_commands (ctx); + if (rc) + { + fprintf (stderr, "%s: failed to the register commands with Assuan: %s\n", + this_pgmname, gpg_strerror (rc)); + return -1; + } + + assuan_register_option_handler (ctx, option_handler); + assuan_register_reset_notify (ctx, pinentry_assuan_reset_handler); + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + break; + else if (rc) + { + fprintf (stderr, "%s: Assuan accept problem: %s\n", + this_pgmname, gpg_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + fprintf (stderr, "%s: Assuan processing failed: %s\n", + this_pgmname, gpg_strerror (rc)); + continue; + } + } + + assuan_release (ctx); + return 0; +} + + +/* Start the pinentry event loop. The program will start to process + Assuan commands until it is finished or an error occurs. If an + error occurs, -1 is returned. Otherwise, 0 is returned. */ +int +pinentry_loop (void) +{ + return pinentry_loop2 (STDIN_FILENO, STDOUT_FILENO); +} diff --git a/pinentry/pinentry.h b/pinentry/pinentry.h new file mode 100644 index 0000000..e154ac5 --- /dev/null +++ b/pinentry/pinentry.h @@ -0,0 +1,280 @@ +/* pinentry.h - The interface for the PIN entry support library. + Copyright (C) 2002, 2003, 2010, 2015 g10 Code GmbH + + This file is part of PINENTRY. + + PINENTRY is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PINENTRY is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . + */ + +#ifndef PINENTRY_H +#define PINENTRY_H + +#ifdef __cplusplus +extern "C" { +#if 0 +} +#endif +#endif + +typedef enum { + PINENTRY_COLOR_NONE, PINENTRY_COLOR_DEFAULT, + PINENTRY_COLOR_BLACK, PINENTRY_COLOR_RED, + PINENTRY_COLOR_GREEN, PINENTRY_COLOR_YELLOW, + PINENTRY_COLOR_BLUE, PINENTRY_COLOR_MAGENTA, + PINENTRY_COLOR_CYAN, PINENTRY_COLOR_WHITE +} pinentry_color_t; + +struct pinentry +{ + /* The window title, or NULL. (Assuan: "SETTITLE TITLE".) */ + char *title; + /* The description to display, or NULL. (Assuan: "SETDESC + DESC".) */ + char *description; + /* The error message to display, or NULL. (Assuan: "SETERROR + MESSAGE".) */ + char *error; + /* The prompt to display, or NULL. (Assuan: "SETPROMPT + prompt".) */ + char *prompt; + /* The OK button text to display, or NULL. (Assuan: "SETOK + OK".) */ + char *ok; + /* The Not-OK button text to display, or NULL. This is the text for + the alternative option shown by the third button. (Assuan: + "SETNOTOK NOTOK".) */ + char *notok; + /* The Cancel button text to display, or NULL. (Assuan: "SETCANCEL + CANCEL".) */ + char *cancel; + + /* The buffer to store the secret into. */ + char *pin; + /* The length of the buffer. */ + int pin_len; + /* Whether the pin was read from an external cache (1) or entered by + the user (0). */ + int pin_from_cache; + + /* The name of the X display to use if X is available and supported. + (Assuan: "OPTION display DISPLAY".) */ + char *display; + /* The name of the terminal node to open if X not available or + supported. (Assuan: "OPTION ttyname TTYNAME".) */ + char *ttyname; + /* The type of the terminal. (Assuan: "OPTION ttytype TTYTYPE".) */ + char *ttytype; + /* The LC_CTYPE value for the terminal. (Assuan: "OPTION lc-ctype + LC_CTYPE".) */ + char *lc_ctype; + /* The LC_MESSAGES value for the terminal. (Assuan: "OPTION + lc-messages LC_MESSAGES".) */ + char *lc_messages; + + /* True if debug mode is requested. */ + int debug; + + /* The number of seconds before giving up while waiting for user input. */ + int timeout; + + /* True if caller should grab the keyboard. (Assuan: "OPTION grab" + or "OPTION no-grab".) */ + int grab; + /* The window ID of the parent window over which the pinentry window + should be displayed. (Assuan: "OPTION parent-wid WID".) */ + int parent_wid; + + /* The name of an optional file which will be touched after a curses + entry has been displayed. (Assuan: "OPTION touch-file + FILENAME".) */ + char *touch_file; + + /* The frontend should set this to -1 if the user canceled the + request, and to the length of the PIN stored in pin + otherwise. */ + int result; + + /* The frontend should set this if the NOTOK button was pressed. */ + int canceled; + + /* The frontend should set this to true if an error with the local + conversion occured. */ + int locale_err; + + /* The frontend should set this to a gpg-error so that commands are + able to return specific error codes. This is an ugly hack due to + the fact that pinentry_cmd_handler_t returns the length of the + passphrase or a negative error code. */ + int specific_err; + + /* The frontend should set this to true if the window close button + has been used. This flag is used in addition to a regular return + value. */ + int close_button; + + /* The caller should set this to true if only one button is + required. This is useful for notification dialogs where only a + dismiss button is required. */ + int one_button; + + /* If true a second prompt for the passphrase is shown and the user + is expected to enter the same passphrase again. Pinentry checks + that both match. (Assuan: "SETREPEAT".) */ + char *repeat_passphrase; + + /* The string to show if a repeated passphrase does not match. + (Assuan: "SETREPEATERROR ERROR".) */ + char *repeat_error_string; + + /* Set to true if the passphrase has been entered a second time and + matches the first passphrase. */ + int repeat_okay; + + /* If this is not NULL, a passphrase quality indicator is shown. + There will also be an inquiry back to the caller to get an + indication of the quality for the passphrase entered so far. The + string is used as a label for the quality bar. (Assuan: + "SETQUALITYBAR LABEL".) */ + char *quality_bar; + + /* The tooltip to be show for the qualitybar. Malloced or NULL. + (Assuan: "SETQUALITYBAR_TT TOOLTIP".) */ + char *quality_bar_tt; + + /* For the curses pinentry, the color of error messages. */ + pinentry_color_t color_fg; + int color_fg_bright; + pinentry_color_t color_bg; + pinentry_color_t color_so; + int color_so_bright; + + /* Malloced and i18ned default strings or NULL. These strings may + include an underscore character to indicate an accelerator key. + A double underscore represents a plain one. */ + /* (Assuan: "OPTION default-ok OK"). */ + char *default_ok; + /* (Assuan: "OPTION default-cancel CANCEL"). */ + char *default_cancel; + /* (Assuan: "OPTION default-prompt PROMPT"). */ + char *default_prompt; + /* (Assuan: "OPTION default-pwmngr + SAVE_PASSWORD_WITH_PASSWORD_MANAGER?"). */ + char *default_pwmngr; + + /* Whether we are allowed to read the password from an external + cache. (Assuan: "OPTION allow-external-password-cache") */ + int allow_external_password_cache; + + /* We only try the cache once. */ + int tried_password_cache; + + /* A stable identifier for the key. (Assuan: "SETKEYINFO + KEYINFO".) */ + char *keyinfo; + + /* Whether we may cache the password (according to the user). */ + int may_cache_password; + + /* NOTE: If you add any additional fields to this structure, be sure + to update the initializer in pinentry/pinentry.c!!! */ + + /* For the quality indicator we need to do an inquiry. Thus we need + to save the assuan ctx. */ + void *ctx_assuan; + +}; +typedef struct pinentry *pinentry_t; + + +/* The pinentry command handler type processes the pinentry request + PIN. If PIN->pin is zero, request a confirmation, otherwise a PIN + entry. On confirmation, the function should return TRUE if + confirmed, and FALSE otherwise. On PIN entry, the function should + return -1 if an error occured or the user cancelled the operation + and 1 otherwise. */ +typedef int (*pinentry_cmd_handler_t) (pinentry_t pin); + +/* Start the pinentry event loop. The program will start to process + Assuan commands until it is finished or an error occurs. If an + error occurs, -1 is returned and errno indicates the type of an + error. Otherwise, 0 is returned. */ +int pinentry_loop (void); + +/* The same as above but allows to specify the i/o descriptors. + * infd and outfd will be duplicated in this function so the caller + * still has to close them if necessary. + */ +int pinentry_loop2 (int infd, int outfd); + + +/* Convert the UTF-8 encoded string TEXT to the encoding given in + LC_CTYPE. Return NULL on error. */ +char *pinentry_utf8_to_local (const char *lc_ctype, const char *text); + +/* Convert TEXT which is encoded according to LC_CTYPE to UTF-8. With + SECURE set to true, use secure memory for the returned buffer. + Return NULL on error. */ +char *pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure); + + +/* Run a quality inquiry for PASSPHRASE of LENGTH. */ +int pinentry_inq_quality (pinentry_t pin, + const char *passphrase, size_t length); + +/* Try to make room for at least LEN bytes for the pin in the pinentry + PIN. Returns new buffer on success and 0 on failure. */ +char *pinentry_setbufferlen (pinentry_t pin, int len); + +/* Use the buffer at BUFFER for PIN->PIN. BUFFER must be NULL or + allocated using secmem_alloc. LEN is the size of the buffer. If + it is unknown, but BUFFER is a NUL terminated string, you pass 0 to + just use strlen(buffer)+1. */ +void pinentry_setbuffer_use (pinentry_t pin, char *buffer, int len); + +/* Initialize the secure memory subsystem, drop privileges and + return. Must be called early. */ +void pinentry_init (const char *pgmname); + +/* Return true if either DISPLAY is set or ARGV contains the string + "--display". */ +int pinentry_have_display (int argc, char **argv); + +/* Parse the command line options. May exit the program if only help + or version output is requested. */ +void pinentry_parse_opts (int argc, char *argv[]); + + +/* The caller must define this variable to process assuan commands. */ +extern pinentry_cmd_handler_t pinentry_cmd_handler; + + + + + +#ifdef HAVE_W32_SYSTEM +/* Windows declares sleep as obsolete, but provides a definition for + _sleep but non for the still existing sleep. */ +#define sleep(a) _sleep ((a)) +#endif /*HAVE_W32_SYSTEM*/ + + + +#if 0 +{ +#endif +#ifdef __cplusplus +} +#endif + +#endif /* PINENTRY_H */ diff --git a/pinentry/secmem++.h b/pinentry/secmem++.h new file mode 100644 index 0000000..88a5d45 --- /dev/null +++ b/pinentry/secmem++.h @@ -0,0 +1,91 @@ +/* STL allocator for secmem + * Copyright (C) 2008 Marc Mutz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SECMEM_SECMEMPP_H__ +#define __SECMEM_SECMEMPP_H__ + +#include "secmem/memory.h" +#include + +namespace secmem { + + template + class alloc { + public: + // type definitions: + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef T value_type; + + // rebind + template + struct rebind { + typedef alloc other; + }; + + // address + pointer address( reference value ) const { + return &value; + } + const_pointer address( const_reference value ) const { + return &value; + } + + // (trivial) ctors and dtors + alloc() {} + alloc( const alloc & ) {} + template alloc( const alloc & ) {} + // copy ctor is ok + ~alloc() {} + + // de/allocation + size_type max_size() const { + return secmem_get_max_size(); + } + + pointer allocate( size_type n, void * =0 ) { + return static_cast( secmem_malloc( n * sizeof(T) ) ); + } + + void deallocate( pointer p, size_type ) { + secmem_free( p ); + } + + // de/construct + void construct( pointer p, const T & value ) { + void * loc = p; + new (loc)T(value); + } + void destruct( pointer p ) { + p->~T(); + } + }; + + // equality comparison + template + bool operator==( const alloc &, const alloc & ) { return true; } + template + bool operator!=( const alloc &, const alloc & ) { return false; } + +} + +#endif /* __SECMEM_SECMEMPP_H__ */ diff --git a/pinentry/secmem-util.h b/pinentry/secmem-util.h new file mode 100644 index 0000000..b422182 --- /dev/null +++ b/pinentry/secmem-util.h @@ -0,0 +1,3 @@ +/* This file exists because "util.h" is such a generic name that it is + likely to clash with other such files. */ +#include "util.h" diff --git a/pinentry/secmem.c b/pinentry/secmem.c new file mode 100644 index 0000000..db25ec3 --- /dev/null +++ b/pinentry/secmem.c @@ -0,0 +1,460 @@ +/* secmem.c - memory allocation from a secure heap + * Copyright (C) 1998, 1999, 2003 Free Software Foundation, Inc. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#if defined(HAVE_MLOCK) || defined(HAVE_MMAP) +# include +# include +# include +# ifdef USE_CAPABILITIES +# include +# endif +#endif +#include + +#include "memory.h" + +#ifdef ORIGINAL_GPG_VERSION +#include "types.h" +#include "util.h" +#else /* ORIGINAL_GPG_VERSION */ + +#include "util.h" + +typedef union { + int a; + short b; + char c[1]; + long d; +#ifdef HAVE_U64_TYPEDEF + u64 e; +#endif + float f; + double g; +} PROPERLY_ALIGNED_TYPE; + +#define log_error log_info +#define log_bug log_fatal + +void +log_info(char *template, ...) +{ + va_list args; + + va_start(args, template); + vfprintf(stderr, template, args); + va_end(args); +} + +void +log_fatal(char *template, ...) +{ + va_list args; + + va_start(args, template); + vfprintf(stderr, template, args); + va_end(args); + exit(EXIT_FAILURE); +} + +#endif /* ORIGINAL_GPG_VERSION */ + +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +# define MAP_ANONYMOUS MAP_ANON +#endif + +#define DEFAULT_POOLSIZE 16384 + +typedef struct memblock_struct MEMBLOCK; +struct memblock_struct { + unsigned size; + union { + MEMBLOCK *next; + PROPERLY_ALIGNED_TYPE aligned; + } u; +}; + + + +static void *pool; +static volatile int pool_okay; /* may be checked in an atexit function */ +static int pool_is_mmapped; +static size_t poolsize; /* allocated length */ +static size_t poollen; /* used length */ +static MEMBLOCK *unused_blocks; +static unsigned max_alloced; +static unsigned cur_alloced; +static unsigned max_blocks; +static unsigned cur_blocks; +static int disable_secmem; +static int show_warning; +static int no_warning; +static int suspend_warning; + + +static void +print_warn(void) +{ + if( !no_warning ) + log_info("Warning: using insecure memory!\n"); +} + + +static void +lock_pool( void *p, size_t n ) +{ +#if defined(USE_CAPABILITIES) && defined(HAVE_MLOCK) + int err; + + cap_set_proc( cap_from_text("cap_ipc_lock+ep") ); + err = mlock( p, n ); + if( err && errno ) + err = errno; + cap_set_proc( cap_from_text("cap_ipc_lock+p") ); + + if( err ) { + if( errno != EPERM + #ifdef EAGAIN /* OpenBSD returns this */ + && errno != EAGAIN + #endif + ) + log_error("can't lock memory: %s\n", strerror(err)); + show_warning = 1; + } + +#elif defined(HAVE_MLOCK) + uid_t uid; + int err; + + uid = getuid(); + +#ifdef HAVE_BROKEN_MLOCK + if( uid ) { + errno = EPERM; + err = errno; + } + else { + err = mlock( p, n ); + if( err && errno ) + err = errno; + } +#else + err = mlock( p, n ); + if( err && errno ) + err = errno; +#endif + + if( uid && !geteuid() ) { + if( setuid( uid ) || getuid() != geteuid() ) + log_fatal("failed to reset uid: %s\n", strerror(errno)); + } + + if( err ) { + if( errno != EPERM +#ifdef EAGAIN /* OpenBSD returns this */ + && errno != EAGAIN +#endif + ) + log_error("can't lock memory: %s\n", strerror(err)); + show_warning = 1; + } + +#else + log_info("Please note that you don't have secure memory on this system\n"); +#endif +} + + +static void +init_pool( size_t n) +{ + size_t pgsize; + + poolsize = n; + + if( disable_secmem ) + log_bug("secure memory is disabled"); + +#ifdef HAVE_GETPAGESIZE + pgsize = getpagesize(); +#else + pgsize = 4096; +#endif + +#if HAVE_MMAP + poolsize = (poolsize + pgsize -1 ) & ~(pgsize-1); +# ifdef MAP_ANONYMOUS + pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); +# else /* map /dev/zero instead */ + { int fd; + + fd = open("/dev/zero", O_RDWR); + if( fd == -1 ) { + log_error("can't open /dev/zero: %s\n", strerror(errno) ); + pool = (void*)-1; + } + else { + pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE, + MAP_PRIVATE, fd, 0); + close (fd); + } + } +# endif + if( pool == (void*)-1 ) + log_info("can't mmap pool of %u bytes: %s - using malloc\n", + (unsigned)poolsize, strerror(errno)); + else { + pool_is_mmapped = 1; + pool_okay = 1; + } + +#endif + if( !pool_okay ) { + pool = malloc( poolsize ); + if( !pool ) + log_fatal("can't allocate memory pool of %u bytes\n", + (unsigned)poolsize); + else + pool_okay = 1; + } + lock_pool( pool, poolsize ); + poollen = 0; +} + + +/* concatenate unused blocks */ +static void +compress_pool(void) +{ + /* fixme: we really should do this */ +} + +void +secmem_set_flags( unsigned flags ) +{ + int was_susp = suspend_warning; + + no_warning = flags & 1; + suspend_warning = flags & 2; + + /* and now issue the warning if it is not longer suspended */ + if( was_susp && !suspend_warning && show_warning ) { + show_warning = 0; + print_warn(); + } +} + +unsigned +secmem_get_flags(void) +{ + unsigned flags; + + flags = no_warning ? 1:0; + flags |= suspend_warning ? 2:0; + return flags; +} + +void +secmem_init( size_t n ) +{ + if( !n ) { +#ifdef USE_CAPABILITIES + /* drop all capabilities */ + cap_set_proc( cap_from_text("all-eip") ); + +#elif !defined(HAVE_DOSISH_SYSTEM) + uid_t uid; + + disable_secmem=1; + uid = getuid(); + if( uid != geteuid() ) { + if( setuid( uid ) || getuid() != geteuid() ) + log_fatal("failed to drop setuid\n" ); + } +#endif + } + else { + if( n < DEFAULT_POOLSIZE ) + n = DEFAULT_POOLSIZE; + if( !pool_okay ) + init_pool(n); + else + log_error("Oops, secure memory pool already initialized\n"); + } +} + + +void * +secmem_malloc( size_t size ) +{ + MEMBLOCK *mb, *mb2; + int compressed=0; + + if( !pool_okay ) { + log_info( + "operation is not possible without initialized secure memory\n"); + log_info("(you may have used the wrong program for this task)\n"); + exit(2); + } + if( show_warning && !suspend_warning ) { + show_warning = 0; + print_warn(); + } + + /* blocks are always a multiple of 32 */ + size += sizeof(MEMBLOCK); + size = ((size + 31) / 32) * 32; + + retry: + /* try to get it from the used blocks */ + for(mb = unused_blocks,mb2=NULL; mb; mb2=mb, mb = mb->u.next ) + if( mb->size >= size ) { + if( mb2 ) + mb2->u.next = mb->u.next; + else + unused_blocks = mb->u.next; + goto leave; + } + /* allocate a new block */ + if( (poollen + size <= poolsize) ) { + mb = (void*)((char*)pool + poollen); + poollen += size; + mb->size = size; + } + else if( !compressed ) { + compressed=1; + compress_pool(); + goto retry; + } + else + return NULL; + + leave: + cur_alloced += mb->size; + cur_blocks++; + if( cur_alloced > max_alloced ) + max_alloced = cur_alloced; + if( cur_blocks > max_blocks ) + max_blocks = cur_blocks; + + memset (&mb->u.aligned.c, 0, + size - (size_t) &((struct memblock_struct *) 0)->u.aligned.c); + + return &mb->u.aligned.c; +} + + +void * +secmem_realloc( void *p, size_t newsize ) +{ + MEMBLOCK *mb; + size_t size; + void *a; + + if (! p) + return secmem_malloc(newsize); + + mb = (MEMBLOCK*)((char*)p - ((size_t) &((MEMBLOCK*)0)->u.aligned.c)); + size = mb->size; + if( newsize < size ) + return p; /* it is easier not to shrink the memory */ + a = secmem_malloc( newsize ); + memcpy(a, p, size); + memset((char*)a+size, 0, newsize-size); + secmem_free(p); + return a; +} + + +void +secmem_free( void *a ) +{ + MEMBLOCK *mb; + size_t size; + + if( !a ) + return; + + mb = (MEMBLOCK*)((char*)a - ((size_t) &((MEMBLOCK*)0)->u.aligned.c)); + size = mb->size; + /* This does not make much sense: probably this memory is held in the + * cache. We do it anyway: */ + wipememory2(mb, 0xff, size ); + wipememory2(mb, 0xaa, size ); + wipememory2(mb, 0x55, size ); + wipememory2(mb, 0x00, size ); + mb->size = size; + mb->u.next = unused_blocks; + unused_blocks = mb; + cur_blocks--; + cur_alloced -= size; +} + +int +m_is_secure( const void *p ) +{ + return p >= pool && p < (void*)((char*)pool+poolsize); +} + +void +secmem_term() +{ + if( !pool_okay ) + return; + + wipememory2( pool, 0xff, poolsize); + wipememory2( pool, 0xaa, poolsize); + wipememory2( pool, 0x55, poolsize); + wipememory2( pool, 0x00, poolsize); +#if HAVE_MMAP + if( pool_is_mmapped ) + munmap( pool, poolsize ); +#endif + pool = NULL; + pool_okay = 0; + poolsize=0; + poollen=0; + unused_blocks=NULL; +} + + +void +secmem_dump_stats() +{ + if( disable_secmem ) + return; + fprintf(stderr, + "secmem usage: %u/%u bytes in %u/%u blocks of pool %lu/%lu\n", + cur_alloced, max_alloced, cur_blocks, max_blocks, + (ulong)poollen, (ulong)poolsize ); +} + + +size_t +secmem_get_max_size (void) +{ + return poolsize; +} diff --git a/pinentry/util.c b/pinentry/util.c new file mode 100644 index 0000000..a47164a --- /dev/null +++ b/pinentry/util.c @@ -0,0 +1,150 @@ +/* Quintuple Agent + * Copyright (C) 1999 Robert Bihlmeyer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE 1 + +#include +#ifndef HAVE_W32CE_SYSTEM +# include +#endif +#include +#include +#include +#include +#include + +#include "util.h" + +#ifndef HAVE_DOSISH_SYSTEM +static int uid_set = 0; +static uid_t real_uid, file_uid; +#endif /*!HAVE_DOSISH_SYSTEM*/ + +/* Write DATA of size BYTES to FD, until all is written or an error + occurs. */ +ssize_t +xwrite(int fd, const void *data, size_t bytes) +{ + char *ptr; + size_t todo; + ssize_t written = 0; + + for (ptr = (char *)data, todo = bytes; todo; ptr += written, todo -= written) + { + do + written = write (fd, ptr, todo); + while ( +#ifdef HAVE_W32CE_SYSTEM + 0 +#else + written == -1 && errno == EINTR +#endif + ); + if (written < 0) + break; + } + return written; +} + +#if 0 +extern int debug; + +int +debugmsg(const char *fmt, ...) +{ + va_list va; + int ret; + + if (debug) { + va_start(va, fmt); + fprintf(stderr, "\e[4m"); + ret = vfprintf(stderr, fmt, va); + fprintf(stderr, "\e[24m"); + va_end(va); + return ret; + } else + return 0; +} +#endif + +/* initialize uid variables */ +#ifndef HAVE_DOSISH_SYSTEM +static void +init_uids(void) +{ + real_uid = getuid(); + file_uid = geteuid(); + uid_set = 1; +} +#endif + + +#if 0 /* Not used. */ +/* lower privileges to the real user's */ +void +lower_privs() +{ + if (!uid_set) + init_uids(); + if (real_uid != file_uid) { +#ifdef HAVE_SETEUID + if (seteuid(real_uid) < 0) { + perror("lowering privileges failed"); + exit(EXIT_FAILURE); + } +#else + fprintf(stderr, _("Warning: running q-agent setuid on this system is dangerous\n")); +#endif /* HAVE_SETEUID */ + } +} +#endif /* if 0 */ + +#if 0 /* Not used. */ +/* raise privileges to the effective user's */ +void +raise_privs() +{ + assert(real_uid >= 0); /* lower_privs() must be called before this */ +#ifdef HAVE_SETEUID + if (real_uid != file_uid && seteuid(file_uid) < 0) { + perror("Warning: raising privileges failed"); + } +#endif /* HAVE_SETEUID */ +} +#endif /* if 0 */ + +/* drop all additional privileges */ +void +drop_privs() +{ +#ifndef HAVE_DOSISH_SYSTEM + if (!uid_set) + init_uids(); + if (real_uid != file_uid) { + if (setuid(real_uid) < 0) { + perror("dropping privileges failed"); + exit(EXIT_FAILURE); + } + file_uid = real_uid; + } +#endif +} diff --git a/pinentry/util.h b/pinentry/util.h new file mode 100644 index 0000000..7986c99 --- /dev/null +++ b/pinentry/util.h @@ -0,0 +1,66 @@ +/* Quintuple Agent utilities + * Copyright (C) 1999 Robert Bihlmeyer + * Copyright (C) 2003 g10 Code GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _UTIL_H +#define _UTIL_H + +#include + +#ifndef HAVE_BYTE_TYPEDEF +# undef byte +# ifdef __riscos__ + /* Norcroft treats char == unsigned char but char* != unsigned char* */ + typedef char byte; +# else + typedef unsigned char byte; +# endif +# define HAVE_BYTE_TYPEDEF +#endif + +#ifndef HAVE_ULONG_TYPEDEF +# undef ulong + typedef unsigned long ulong; +# define HAVE_ULONG_TYPEDEF +#endif + + +ssize_t xwrite(int, const void *, size_t); /* write until finished */ +int debugmsg(const char *, ...); /* output a debug message if debugging==on */ +void drop_privs(void); /* finally drop privileges */ + + +/* To avoid that a compiler optimizes certain memset calls away, these + macros may be used instead. */ +#define wipememory2(_ptr,_set,_len) do { \ + volatile char *_vptr=(volatile char *)(_ptr); \ + size_t _vlen=(_len); \ + while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ + } while(0) +#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) +#define wipe(_ptr,_len) wipememory2(_ptr,0,_len) + + + + +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) + + +#endif diff --git a/test b/test new file mode 100755 index 0000000..c63c8ce --- /dev/null +++ b/test @@ -0,0 +1,43 @@ +#!/bin/sh + +if [[ $1 -eq 1 ]]; then + +echo "SETTITLE title +SETPROMPT prompt +SETDESC PROMPT +GETPIN +BYE" | ./pinentry-dmenu + +elif [[ $1 -eq 2 ]]; then + +echo "SETTITLE title +SETPROMPT confirm +SETDESC CONFIRM +confirm +BYE" | ./pinentry-dmenu + +elif [[ $1 -eq 3 ]]; then + +echo "SETTITLE title +SETPROMPT prompt +SETDESC REPEAT +SETREPEAT repeat +GETPIN +BYE" | ./pinentry-dmenu + +else + +echo "SETTITLE title +SETPROMPT prompt +SETDESC PROMPT +GETPIN +SETPROMPT confirm +SETDESC CONFIRM +confirm +SETPROMPT repeat +SETDESC REPEAT +SETREPEAT +GETPIN +BYE" | ./pinentry-dmenu + +fi diff --git a/util.c b/util.c new file mode 100644 index 0000000..fe044fc --- /dev/null +++ b/util.c @@ -0,0 +1,35 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size);