[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: [Raspbian-devel] libpam-ubico and signed char on arm in debian and derivatives



Simon Josefsson wrote:
tor 2013-03-07 klockan 13:51 +0000 skrev peter green:
I am a cofounder of a project called raspbian to provide a hard float derivative of debian for the raspberry pi. A user reported a bug to us about libpam-ubico related (so the reported claims) to char signedness and linked to a commit in upstream git.

https://bugs.launchpad.net/raspbian/+bug/1039577
https://github.com/Yubico/yubico-c-client/commit/6fcc3d49d1d9b733c5bd04e4e60d400ed97cda40

If this can be reproduced on an official debian port then IMO it's a grave bug. However I don't own a yubikey myself so there is no way I can test it and I don't feel comfortable filing a grave bug in debian that I can't reproduce myself.

Hi!  If you could build and run the self-test of the ykclient package on
an internet-connected machine, that should hopefully trigger the bug.
If so, please file a bug.  Version 2.9 should fix this problem, and
doesn't contain anything critical, so maybe it could be uploaded if
indeed this is a grave bug.  I don't have access to any armel devices
easily.
Ok, the plot thickens.

It seems the signed char bug in the base64 code was made apparent in
debian when they reenabled the testsuite with version 2.8-1 and was
fixed by version 2.8-2. Unfortunately 2.8-1 didn't migrate to testing
because of build failures and 2.8-2 didn't migrate to testing because
of the freeze. Below is a diffstat between the version in testing and
the version in unstable.

plugwash@raspbian:~$ debdiff /home/repo/sourcearchive/main/y/ykclient/ykclient_2.6-1.dsc /home/repo/sourcearchive/main/y/ykclient/ykclient_2.8-2.dsc | diffstat COPYING | 2 ChangeLog | 195 +++ Makefile.am | 8 Makefile.in | 18 NEWS | 29 README | 15 aclocal.m4 | 256 ++++ b64/cdecode.c | 15 config.guess | 223 ++--
config.sub                 |  156 +-
configure                  | 1767 ++++++++++++++++++++++----------
configure.ac | 13 debian/changelog | 17 debian/control | 2 debian/rules | 1 ltmain.sh | 2437 ++++++++++++++++++++++++++++++---------------
m4/libcurl.m4              |  240 ----
m4/libtool.m4              | 1078 +++++++++++++------
m4/ltversion.m4 | 12 simple.mk | 2 tests/Makefile.am | 6 tests/Makefile.in | 16 tests/selftest.c | 52 tool.c | 37 ykclient.c | 641 +++++++---- ykclient.h | 17 ykclient_server_response.c | 41 ykclient_server_response.h | 2 28 files changed, 4920 insertions(+), 2378 deletions(-)
plugwash@raspbian:~$

Even with the autohell stuff filtered out the debdiff (filtered debdiff attached) still looks pretty intimidating. Release team how should we play this? do you want
to unblock the version in sid or should we look into backporting the base64 fix to
the version in wheezy?

diff -Nru ykclient-2.6/aclocal.m4 ykclient-2.8/aclocal.m4
diff -Nru ykclient-2.6/b64/cdecode.c ykclient-2.8/b64/cdecode.c
--- ykclient-2.6/b64/cdecode.c	2011-02-22 14:04:46.000000000 +0000
+++ ykclient-2.8/b64/cdecode.c	2013-03-08 01:00:50.000000000 +0000
@@ -9,10 +9,11 @@
 
 int base64_decode_value(char value_in)
 {
-	static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
+	static const signed char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
 	static const char decoding_size = sizeof(decoding);
+	if (value_in < 43) return -1;
 	value_in -= 43;
-	if (value_in < 0 || value_in > decoding_size) return -1;
+	if (value_in > decoding_size) return -1;
 	return decoding[(int)value_in];
 }
 
@@ -26,7 +27,7 @@
 {
 	const char* codechar = code_in;
 	char* plainchar = plaintext_out;
-	char fragment;
+	int fragment;
 	
 	*plainchar = state_in->plainchar;
 	
@@ -42,7 +43,7 @@
 					state_in->plainchar = *plainchar;
 					return plainchar - plaintext_out;
 				}
-				fragment = (char)base64_decode_value(*codechar++);
+				fragment = base64_decode_value(*codechar++);
 			} while (fragment < 0);
 			*plainchar    = (fragment & 0x03f) << 2;
 	case step_b:
@@ -53,7 +54,7 @@
 					state_in->plainchar = *plainchar;
 					return plainchar - plaintext_out;
 				}
-				fragment = (char)base64_decode_value(*codechar++);
+				fragment = base64_decode_value(*codechar++);
 			} while (fragment < 0);
 			*plainchar++ |= (fragment & 0x030) >> 4;
 			*plainchar    = (fragment & 0x00f) << 4;
@@ -65,7 +66,7 @@
 					state_in->plainchar = *plainchar;
 					return plainchar - plaintext_out;
 				}
-				fragment = (char)base64_decode_value(*codechar++);
+				fragment = base64_decode_value(*codechar++);
 			} while (fragment < 0);
 			*plainchar++ |= (fragment & 0x03c) >> 2;
 			*plainchar    = (fragment & 0x003) << 6;
@@ -77,7 +78,7 @@
 					state_in->plainchar = *plainchar;
 					return plainchar - plaintext_out;
 				}
-				fragment = (char)base64_decode_value(*codechar++);
+				fragment = base64_decode_value(*codechar++);
 			} while (fragment < 0);
 			*plainchar++   |= (fragment & 0x03f);
 		}
diff -Nru ykclient-2.6/ChangeLog ykclient-2.8/ChangeLog
--- ykclient-2.6/ChangeLog	2011-06-06 18:28:41.000000000 +0100
+++ ykclient-2.8/ChangeLog	2012-06-15 13:25:23.000000000 +0100
@@ -1,3 +1,181 @@
+2012-06-15  Fredrik Thulin <fredrik@yubico.com>
+
+	* tests/Makefile.am: Test cases need cURL now.
+
+2012-06-15  Klas Lindfors <klas@yubico.com>
+
+	* NEWS: NEWS for version 2.8
+
+2012-06-15  Klas Lindfors <klas@yubico.com>
+
+	* README, configure.ac: remove -Wno-extra-portability, it breaks on
+	automake before 1.11.2
+
+2012-06-15  Fredrik Thulin <fredrik@yubico.com>
+
+	* tool.c: Remove extra %'s in usage output.
+
+2012-06-07  Klas Lindfors <klas@yubico.com>
+
+	* m4/.placeholder: placeholder file so the m4 directory exists
+
+2012-06-05  Clemens Lang <neverpanic@gmail.com>
+
+	* configure.ac: Silence warning: missing AM_PROG_AR on non-POSIX Automake 1.12 complains: "linking libraries using a non-POSIX
+	archiver requires 'AM_PROG_AR' in 'configure.ac'". This patch
+	silences this warning, allowing automake 1.12 to process
+	configure.ac with -Werror
+
+2012-05-30  Klas Lindfors <klas@yubico.com>
+
+	* m4/libcurl.m4: remove libcurl.m4, should be present in the system
+	instead
+
+2012-05-30  Klas Lindfors <klas@yubico.com>
+
+	* ykclient.c: move around nonce allocation allocate space for the nonce in ykclient_request(), only using
+	ykc->nonce for user supplied nonce.
+
+2012-05-30  Klas Lindfors <klas@yubico.com>
+
+	* ykclient.c: remove old comment about only one url, no longer
+	accurate
+
+2012-05-29  Klas Lindfors <klas@yubico.com>
+
+	* configure.ac: library code modified and interfaces added
+
+2012-05-28  Klas Lindfors <klas@yubico.com>
+
+	* ykclient.c: do url-encoding of the passed in OTP.
+
+2012-05-28  Klas Lindfors <klas@yubico.com>
+
+	* ykclient.c: generate a new nonce for each request unless the nonce
+	is set by the user
+
+2012-05-23  Jay Kline <jay.kline.ctr@hpcmo.hpc.mil>
+
+	* tool.c: Add copyright notice
+
+2012-05-16  Klas Lindfors <klas@yubico.com>
+
+	* tests/selftest.c: do curl_clobal_init() and curl_global_cleanup()
+	so valgrind is quiter
+
+2012-05-15  Klas Lindfors <klas@yubico.com>
+
+	* ykclient.c: move up variable declarations to scope start
+
+2012-05-15  Simon Josefsson <simon@josefsson.org>
+
+	* ykclient.c: Silence valgrind.
+
+2012-05-14  Klas Lindfors <klas@yubico.com>
+
+	* ykclient.c: use curl_easy_escape() to do url escaping of signature
+
+2012-05-14  Klas Lindfors <klas@yubico.com>
+
+	* ykclient.c, ykclient.h: check malloc and num_templates in
+	set_url_templates() let it return int
+
+2012-05-14  Klas Lindfors <klas@yubico.com>
+
+	* ykclient.c: forgotten free, leaked on every call.
+
+2012-05-14  Klas Lindfors <klas@yubico.com>
+
+	* tests/selftest.c: run the main bulk of the tests with default url add test for setting several urls and doing request
+
+2012-05-14  Klas Lindfors <klas@yubico.com>
+
+	* ykclient.c, ykclient.h: add features to use curl_multi to speak
+	with several validation servers add ykc_set_url_templates(), all other work only internal,
+	interfaces kept.
+
+2012-05-04  Jay Kline <jay.kline.ctr@hpcmo.hpc.mil>
+
+	* tool.c: Add option to specify CA path Add the --ca option to the utility to allow users to specify a
+	separate CA path
+
+2012-05-04  Jay Kline <jay.kline.ctr@hpcmo.hpc.mil>
+
+	* ykclient_server_response.c: Perform memset after checking malloc
+	not after
+
+2012-04-27  Lee Hinman <lee.hinman.ctr@hpc.mil>
+
+	* ykclient.c: Fixed segfault caused by curl error - libcurl error were causing ykclient_request to free resources
+	  before they had been allocated - also added a fprintf to stderr to print out the curl error, the   generic one did not provide enough info for someone to debug
+
+2012-03-21  Jay Kline <jay.kline.ctr@hpcmo.hpc.mil>
+
+	* ykclient.c, ykclient_server_response.c: Fix segfaults  * Added memset after malloc of structs
+
+2012-01-23  Simon Josefsson <simon@josefsson.org>
+
+	* configure.ac: Use silent rules.
+
+2012-01-23  Simon Josefsson <simon@josefsson.org>
+
+	* NEWS, configure.ac, ykclient.h: Add C++ protection.
+
+2012-01-23  Simon Josefsson <simon@josefsson.org>
+
+	* COPYING, Makefile.am, README, configure.ac, simple.mk,
+	tests/Makefile.am, tests/selftest.c, tool.c, ykclient.c,
+	ykclient.h, ykclient_server_response.c, ykclient_server_response.h: 
+	Bump copyright years.
+
+2011-11-23  Simon Josefsson <simon@josefsson.org>
+
+	* README, configure.ac: Shift blame.
+
+2011-11-23  Fredrik Thulin <fredrik@yubico.com>
+
+	* NEWS: Release date for v2.7.
+
+2011-11-23  Fredrik Thulin <fredrik@yubico.com>
+
+	* Makefile.am: distcheck: fix test of simple.mk
+
+2011-11-17  Fredrik Thulin <fredrik@yubico.com>
+
+	* NEWS, configure.ac: Prepare version 2.7.
+
+2011-11-17  Fredrik Thulin <fredrik@yubico.com>
+
+	* Makefile.am: Two srcdir fixes in release target.
+
+2011-11-17  Fredrik Thulin <fredrik@yubico.com>
+
+	* ykclient_server_response.c: Return YKCLIENT_BAD_SERVER_SIGNATURE
+	on missing signature.  This differentiates missing signature from no (other) parameters
+	returned or general parse failures.
+
+2011-11-17  Fredrik Thulin <fredrik@yubico.com>
+
+	* tests/selftest.c, ykclient.c: Verify signatures by default when we
+	can.  Problem reported by Dominic Rutherford
+	<dominic@rutherfordfamily.co.uk>.
+
+2011-06-06  Simon Josefsson <--all>
+
+	* NEWS, configure.ac: Bump versions.
+
+2011-06-06  Simon Josefsson <--all>
+
+	* .gitignore: Update.
+
+2011-06-06  Simon Josefsson <--all>
+
+	* tool.c: Remove unused parameter.
+
+2011-06-06  Simon Josefsson <--all>
+
+	* ykclient_server_response.c: Silence compiler warnings.  Reported by Jussi Sallinen <jussi@jus.si>.
+
 2011-06-06  Simon Josefsson <--all>
 
 	* NEWS: Version 2.6.
@@ -45,10 +223,11 @@
 
 	* configure.ac: Prepare for release 2.5.  Library code modified and interfaces added.
 
-2011-05-30  Fredrik Thulin <fredrik@yubico.com>
+2011-03-11  Tollef Fog Heen <tfheen@err.no>
 
-	* : commit 5ef5b810b1be12a7883612d65a361d0d3be01324 Author: Tollef
-	Fog Heen <tfheen@err.no> Date:   Fri Mar 11 14:02:55 2011 +0100
+	* tests/Makefile.am, tests/selftest.c: Make test build without
+	ykclient being installed first Add missing -I flag to ensure we get the right ykclient.h when
+	building the self test
 
 2011-03-08  Fredrik Thulin <fredrik@yubico.com>
 
@@ -83,8 +262,10 @@
 
 2011-03-04  Fredrik Thulin <fredrik@yubico.com>
 
-	* : commit c40063ae88a96a1dad9640e803823b8115ce32c2 Author: Fredrik
-	Thulin <fredrik@yubico.com> Date:   Fri Mar 4 10:24:31 2011 +0100
+	* ykclient.c: Only verify our nonce/OTP is in status=OK responses.  A MITM will always be able to get the server to send signed
+	responses about invalid HMAC in request for example, so there is no
+	need to have nonce/OTP in those responses (and at least one
+	validation server implementation does not include it in those).
 
 2011-03-03  Fredrik Thulin <fredrik@yubico.com>
 
@@ -135,8 +316,8 @@
 
 2011-02-23  Fredrik Thulin <fredrik@yubico.com>
 
-	* : commit 4c4c7cc53ec411df7d35a5399df397ad7c526075 Author: Fredrik
-	Thulin <fredrik@yubico.com> Date:   Wed Feb 23 10:34:39 2011 +0100
+	* configure.ac, ykclient.h: Minor changes after pre-release review
+	by Simon.
 
 2011-02-23  Fredrik Thulin <fredrik@yubico.com>
 
diff -Nru ykclient-2.6/config.guess ykclient-2.8/config.guess
diff -Nru ykclient-2.6/config.sub ykclient-2.8/config.sub
diff -Nru ykclient-2.6/configure ykclient-2.8/configure
diff -Nru ykclient-2.6/configure.ac ykclient-2.8/configure.ac
--- ykclient-2.6/configure.ac	2011-06-06 18:21:04.000000000 +0100
+++ ykclient-2.8/configure.ac	2012-06-15 13:19:27.000000000 +0100
@@ -1,5 +1,5 @@
 # Written by Simon Josefsson <simon@josefsson.org>.
-# Copyright (c) 2008, 2009, 2011 Yubico AB
+# Copyright (c) 2008-2012 Yubico AB
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -26,18 +26,19 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-AC_INIT([ykclient], [2.6], [simon@yubico.com])
+AC_INIT([ykclient], [2.8], [yubico-devel@googlegroups.com])
 AC_CONFIG_MACRO_DIR([m4])
 
 # Library code modified:                              REVISION++
 # Interfaces changed/added/removed:   CURRENT++       REVISION=0
 # Interfaces added:                             AGE++
 # Interfaces removed:                           AGE=0
-AC_SUBST(LT_CURRENT, 6)
-AC_SUBST(LT_REVISION, 1)
-AC_SUBST(LT_AGE, 3)
+AC_SUBST(LT_CURRENT, 7)
+AC_SUBST(LT_REVISION, 0)
+AC_SUBST(LT_AGE, 4)
 
-AM_INIT_AUTOMAKE([-Wall -Werror])
+AM_INIT_AUTOMAKE([1.10 -Wall -Werror])
+# AM_SILENT_RULES([yes])
 AC_PROG_CC
 AC_LIBTOOL_WIN32_DLL
 AC_PROG_LIBTOOL
diff -Nru ykclient-2.6/COPYING ykclient-2.8/COPYING
--- ykclient-2.6/COPYING	2011-02-22 14:04:46.000000000 +0000
+++ ykclient-2.8/COPYING	2012-05-14 14:14:04.000000000 +0100
@@ -1,4 +1,4 @@
-Copyright (c) 2006, 2007, 2008, 2009 Yubico AB
+Copyright (c) 2006-2012 Yubico AB
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff -Nru ykclient-2.6/debian/changelog ykclient-2.8/debian/changelog
--- ykclient-2.6/debian/changelog	2013-03-08 01:00:50.000000000 +0000
+++ ykclient-2.8/debian/changelog	2013-03-08 01:00:50.000000000 +0000
@@ -1,3 +1,20 @@
+ykclient (2.8-2) unstable; urgency=low
+
+  * Fix FTBFS on signed-char architectures.  Thanks to Jakub Wilk for the
+    patch.  Closes: #694804.
+  * Apply patch from Ubuntu to disable network for tests.  Thanks to
+    Michael Terry for the patch.  Closes: #682873
+
+ -- Tollef Fog Heen <tfheen@debian.org>  Fri, 21 Dec 2012 19:48:28 +0100
+
+ykclient (2.8-1) unstable; urgency=low
+
+  * New upstream release
+  * Enable tests again
+  * Add Fredrik and Simon to uploaders as well as DM-Upload-Allowed: yes
+
+ -- Tollef Fog Heen <tfheen@debian.org>  Fri, 29 Jun 2012 07:56:18 +0200
+
 ykclient (2.6-1) unstable; urgency=low
 
   * New upstream release
diff -Nru ykclient-2.6/debian/control ykclient-2.8/debian/control
--- ykclient-2.6/debian/control	2013-03-08 01:00:50.000000000 +0000
+++ ykclient-2.8/debian/control	2013-03-08 01:00:50.000000000 +0000
@@ -1,6 +1,8 @@
 Source: ykclient
 Priority: optional
 Maintainer: Tollef Fog Heen <tfheen@debian.org>
+Uploaders: Fredrik Thulin <fredrik@yubico.com>, Simon Josefsson <simon@yubico.com>
+DM-Upload-Allowed: yes
 Build-Depends: debhelper (>= 7.0.50), autotools-dev, libcurl4-gnutls-dev | libcurl4-openssl-dev, chrpath
 Standards-Version: 3.8.3
 Section: libs
diff -Nru ykclient-2.6/debian/rules ykclient-2.8/debian/rules
--- ykclient-2.6/debian/rules	2013-03-08 01:00:50.000000000 +0000
+++ ykclient-2.8/debian/rules	2013-03-08 01:00:50.000000000 +0000
@@ -18,3 +18,4 @@
 	dh_install --builddirectory=build --fail-missing
 
 override_dh_auto_test:
+	dh_auto_test -- CFLAGS="-DTEST_WITHOUT_INTERNET" 
diff -Nru ykclient-2.6/ltmain.sh ykclient-2.8/ltmain.sh
diff -Nru ykclient-2.6/m4/libcurl.m4 ykclient-2.8/m4/libcurl.m4
diff -Nru ykclient-2.6/m4/libtool.m4 ykclient-2.8/m4/libtool.m4
diff -Nru ykclient-2.6/m4/ltversion.m4 ykclient-2.8/m4/ltversion.m4
diff -Nru ykclient-2.6/Makefile.am ykclient-2.8/Makefile.am
--- ykclient-2.6/Makefile.am	2011-06-06 18:25:25.000000000 +0100
+++ ykclient-2.8/Makefile.am	2012-06-15 13:18:01.000000000 +0100
@@ -1,5 +1,5 @@
 # Written by Simon Josefsson <simon@josefsson.org>.
-# Copyright (c) 2008, 2009, 2011 Yubico AB
+# Copyright (c) 2008-2012 Yubico AB
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -70,12 +70,14 @@
 		echo "  make release USER=[GOOGLEUSERNAME] KEYID=[PGPKEYID]"; \
 		echo "For example:"; \
 		echo "  make release USER=simon@yubico.com KEYID=2117364A"; \
+		echo "  make release USER=fredrikyubico@gmail.com KEYID=4EAA4232"; \
 		exit 1; \
 	fi
 	@head -3 $(srcdir)/NEWS | grep -q "Version $(VERSION) .released `date -I`" || \
 		(echo 'error: Update date/version in $(srcdir)/NEWS.'; exit 1)
-	make -f simple.mk check
-	rm -f ChangeLog
+# check that non-autoconf build system works
+	cd $(srcdir) &&	make -f simple.mk check clean
+	rm -f $(srcdir)/ChangeLog
 	make ChangeLog distcheck
 	gpg --detach-sign --default-key $(KEYID) $(PACKAGE)-$(VERSION).tar.gz
 	gpg --verify $(PACKAGE)-$(VERSION).tar.gz.sig
diff -Nru ykclient-2.6/Makefile.in ykclient-2.8/Makefile.in
diff -Nru ykclient-2.6/NEWS ykclient-2.8/NEWS
--- ykclient-2.6/NEWS	2011-06-06 18:27:58.000000000 +0100
+++ ykclient-2.8/NEWS	2012-06-15 13:14:08.000000000 +0100
@@ -1,5 +1,34 @@
 Yubikey-c-client NEWS -- History of user-visible changes.      -*- outline -*-
 
+* Version 2.8 (released 2012-06-15)
+
+** ykclient: Add C++ namespace protection.
+
+** Add multi-server support with curl_multi.
+   Enabled by default for YubiCloud servers.
+   Settable with the new library function set_template_urls() or
+   the urls parameter to ykclient_verify_otp_v2().
+
+** Remove extra % in ykclient help.
+
+** Add ca path option to ykclient, --ca.
+   Patch from Jay Kline <jay.kline.ctr@hpcmo.hpc.mil>.
+
+** Make the nonce unique for consecutive calls to the same ykclient handle.
+
+** Do url encoding of OTP before sending.
+
+** Fix segfault on curl error.
+   Patch from Lee Hinman <lee.hinman.ctr@hpc.mil>
+
+* Version 2.7 (released 2011-11-23)
+
+** Verify server signatures per default when a key is provided. The old
+behavior can be restored with ykclient_set_verify_signature (ykc, 0);
+Reported by Dominic Rutherford <dominic@rutherfordfamily.co.uk>.
+
+** Return YKCLIENT_BAD_SERVER_SIGNATURE on missing signature.
+
 * Version 2.6 (released 2011-06-06)
 
 ** Use fprintf+exit instead of glibc-ism errx.
diff -Nru ykclient-2.6/README ykclient-2.8/README
--- ykclient-2.6/README	2011-02-22 14:04:46.000000000 +0000
+++ ykclient-2.8/README	2012-06-15 13:14:08.000000000 +0100
@@ -20,6 +20,15 @@
 * configure.ac, Makefile.am: Autoconf/Automake files.
 
 
+License
+-------
+
+The project is licensed under a BSD license.  See the file COPYING for
+exact wording.  For any copyright year range specified as YYYY-ZZZZ in
+this package note that the range specifies every single year in that
+closed interval.
+
+
 Building from Git
 -----------------
 
@@ -46,6 +55,10 @@
   $ autoreconf --install
 -----------
 
+If you get "linking libraries using a non-POSIX archiver requires 'AM_PROG_AR' in 'configure.ac'"
+you're probably using automake 1.12, use 1.11 or add -Wno-extra-portability in configure.ac. Adding
+this breaks compability with automake before 1.11.2.
+
 
 Building
 --------
@@ -105,4 +118,4 @@
 Questions?
 ----------
 
-Talk to <simon@yubico.com>.
+Talk to <yubico-devel@googlegroups.com>.
diff -Nru ykclient-2.6/simple.mk ykclient-2.8/simple.mk
--- ykclient-2.6/simple.mk	2011-06-06 18:24:54.000000000 +0100
+++ ykclient-2.8/simple.mk	2012-05-14 14:14:04.000000000 +0100
@@ -1,7 +1,7 @@
 # Makefile --- Instructions for make to build Yubikey client library and tools.
 #
 # Written by Simon Josefsson <simon@josefsson.org>.
-# Copyright (c) 2006, 2007, 2008, 2009, 2011 Yubico AB
+# Copyright (c) 2006-2012 Yubico AB
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
diff -Nru ykclient-2.6/tests/Makefile.am ykclient-2.8/tests/Makefile.am
--- ykclient-2.6/tests/Makefile.am	2013-03-08 01:00:50.000000000 +0000
+++ ykclient-2.8/tests/Makefile.am	2012-06-15 13:24:14.000000000 +0100
@@ -1,5 +1,5 @@
 # Written by Simon Josefsson <simon@josefsson.org>.
-# Copyright (c) 2011 Yubico AB
+# Copyright (c) 2011-2012 Yubico AB
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -28,10 +28,10 @@
 
 # Self tests.
 
+AM_CPPFLAGS = @LIBCURL_CPPFLAGS@
 AM_LDFLAGS = -no-install
 AM_CFLAGS=-I$(srcdir)/..
-LDADD = ../libykclient.la
-AM_CPPFLAGS = -I$(top_srcdir)
+LDADD = ../libykclient.la -lcurl
 
 check_PROGRAMS = selftest
 TESTS = $(check_PROGRAMS)
diff -Nru ykclient-2.6/tests/Makefile.in ykclient-2.8/tests/Makefile.in
diff -Nru ykclient-2.6/tests/selftest.c ykclient-2.8/tests/selftest.c
--- ykclient-2.6/tests/selftest.c	2011-05-30 20:52:12.000000000 +0100
+++ ykclient-2.8/tests/selftest.c	2012-06-15 08:48:21.000000000 +0100
@@ -1,7 +1,7 @@
 /* selftest.c --- Self-tests for Yubico client library.
  *
  * Written by Simon Josefsson <simon@josefsson.org>.
- * Copyright (c) 2006, 2007, 2008, 2009, 2011 Yubico AB
+ * Copyright (c) 2006-2012 Yubico AB
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -34,6 +34,8 @@
 #include <stdio.h>
 #include <assert.h>
 
+#include <curl/curl.h>
+
 #define TEST(xX) printf ("\nTest %s:%d (%s): ", __FILE__, __LINE__, __FUNCTION__); \
   printf xX; \
   printf ("\n")
@@ -97,14 +99,13 @@
   ykclient_t *ykc;
   int ret;
 
+  curl_global_init(CURL_GLOBAL_ALL);
+
   TEST(("init self"));
   ret = ykclient_init (&ykc);
   printf ("ykclient_init (%d): %s\n", ret, ykclient_strerror (ret));
   assert(ret == YKCLIENT_OK);
 
-  ykclient_set_url_template
-    (ykc, "http://api.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";);
-
   TEST(("null client_id, expect REPLAYED_OTP"));
   ykclient_set_verify_signature(ykc, 0);
   ykclient_set_client (ykc, client_id, 0, NULL);
@@ -188,7 +189,26 @@
   assert (ret == YKCLIENT_OK);
 
 #ifndef TEST_WITHOUT_INTERNET
+  /* When the server dislikes our signature, it will sign the response with a
+     NULL key, so the API call will fail with BAD_SERVER_SIGNATURE even though
+     the server returned status=BAD_SIGNATURE.
+  */
+  TEST(("validation request, expect BAD_SERVER_SIGNATURE"));
+  ret = ykclient_request (ykc, "dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh");
+  printf ("ykclient_request (%d): %s\n", ret, ykclient_strerror (ret));
+  printf ("used url: %s\n", ykclient_get_last_url (ykc));
+  assert (ret == YKCLIENT_BAD_SERVER_SIGNATURE);
+#else
+  printf ("Test SKIPPED\n");
+#endif
+
+#ifndef TEST_WITHOUT_INTERNET
+  /* Now, disable our checking of the servers signature to get the error
+     the server returned (server will use 00000 as key when signing this
+     error response).
+  */
   TEST(("validation request, expect BAD_SIGNATURE"));
+  ykclient_set_verify_signature (ykc, 0);
   ret = ykclient_request (ykc, "dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh");
   printf ("ykclient_request (%d): %s\n", ret, ykclient_strerror (ret));
   printf ("used url: %s\n", ykclient_get_last_url (ykc));
@@ -213,8 +233,7 @@
 #endif
 
   TEST(("set WS 2.0 URL template"));
-  /* Same URL used by library, somewhat silly but still verifies the
-     code path. */
+  /* Set one URL and run tests with that. */
   ykclient_set_url_template
     (ykc, "http://api.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";);
 
@@ -254,6 +273,25 @@
   printf ("Test SKIPPED\n");
 #endif
 
+  TEST(("Set and use several V2.0 URLs"));
+  const char *templates[] = {
+    "http://api.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+    "http://api2.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+    "http://api3.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+    "http://api4.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+    "http://api5.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+  };
+  ykclient_set_url_templates(ykc, 5, templates);
+  ykclient_set_client (ykc, client_id, 20, client_key);
+#ifndef TEST_WITHOUT_INTERNET
+  ret = ykclient_request (ykc, "dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh");
+  printf ("ykclient_request (%d): %s\n", ret, ykclient_strerror (ret));
+  printf ("used url: %s\n", ykclient_get_last_url (ykc));
+  assert (ret == YKCLIENT_REPLAYED_OTP);
+#else
+  printf ("Test SKIPPED\n");
+#endif
+
   ykclient_done (&ykc);
 
   TEST(("strerror 0"));
@@ -268,5 +306,7 @@
 
   printf ("All tests passed\n");
 
+  curl_global_cleanup();
+
   return 0;
 }
diff -Nru ykclient-2.6/tool.c ykclient-2.8/tool.c
--- ykclient-2.6/tool.c	2011-06-06 18:12:08.000000000 +0100
+++ ykclient-2.8/tool.c	2012-06-15 08:49:47.000000000 +0100
@@ -1,6 +1,7 @@
 /* tool.c --- Command line interface to libykclient.
  *
- * Copyright (c) 2006, 2007, 2008, 2009, 2011 Yubico AB
+ * Copyright (c) 2006-2012 Yubico AB
+ * Copyright (c) 2012 Secure Mission Solutions
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -44,11 +45,13 @@
   "  yubikey_otp: One-time password generated by yubikey\n"
   "\n"
   "  Options :\n"
-  "    --url URL		Validation service URL (eg: \"http://api.yubico.com/wsapi/verify?id=%%d&otp=%%s\";)\n"
+  "    --url URL		Validation service URL (eg: \"http://api.yubico.com/wsapi/verify?id=%d&otp=%s\";)\n"
+  "    --ca CA			Path to directory containing the CAs (eg: \"/usr/local/etc/CERTS\")\n"
   "    --apikey Key		API key for HMAC validation of request/response\n";
 
 static struct option long_options[] = {
   {"url", 1, 0, 'u'},
+  {"ca", 1, 0, 'c'},
   {"apikey", 1, 0, 'a'},
   {0, 0, 0, 0}
 };
@@ -56,7 +59,7 @@
 /* Parse command line parameters. */
 void
 parse_args (int argc, char *argv[],
-	    int *client_id, char **token, char **url, char **api_key)
+	    int *client_id, char **token, char **url, char **ca, char **api_key)
 {
   while (1)
     {
@@ -87,6 +90,14 @@
 	    }
 	  *url = optarg;
 	  break;
+	case 'c':
+	  if (strlen (optarg) < 1)
+	    {
+	      fprintf (stderr, "error: must give a valid directory containing CAs");
+	      exit (EXIT_FAILURE);
+	    }
+	  *ca = optarg;
+	  break;
 	}
     }
 
@@ -119,22 +130,34 @@
 main (int argc, char *argv[])
 {
   int client_id;
-  char *token, *url = NULL, *api_key = NULL;
-  int ret, optind;
+  char *token, *url = NULL, *ca = NULL, *api_key = NULL;
+  int ret;
+  ykclient_t *ykc = NULL;
+
+  parse_args (argc, argv, &client_id, &token, &url, &ca, &api_key);
+
+  if (ca)
+  {
+    ret = ykclient_init (&ykc);
+    if (ret != YKCLIENT_OK)
+      return EXIT_FAILURE;
 
-  parse_args (argc, argv, &client_id, &token, &url, &api_key);
+    ykclient_set_ca_path (ykc, ca);
+  }
 
   /* Debug. */
   fprintf (stderr, "Input:\n");
   if (url)
     fprintf (stderr, "  validation URL: %s\n", url);
+  if (ca)
+    fprintf (stderr, "  CA Path: %s\n", ca);
   fprintf (stderr, "  client id: %d\n", client_id);
   fprintf (stderr, "  token: %s\n", token);
   if (api_key != NULL)
     fprintf (stderr, "  api key: %s\n", api_key);
 
   ret =
-    ykclient_verify_otp_v2 (NULL, token, client_id, NULL, 1,
+    ykclient_verify_otp_v2 ( ykc, token, client_id, NULL, 1,
 			    (const char **) &url, api_key);
 
   printf ("Verification output (%d): %s\n", ret, ykclient_strerror (ret));
diff -Nru ykclient-2.6/ykclient.c ykclient-2.8/ykclient.c
--- ykclient-2.6/ykclient.c	2011-06-06 18:12:08.000000000 +0100
+++ ykclient-2.8/ykclient.c	2012-06-15 08:48:21.000000000 +0100
@@ -1,7 +1,7 @@
 /* ykclient.c --- Implementation of Yubikey OTP validation client library.
  *
  * Written by Simon Josefsson <simon@josefsson.org>.
- * Copyright (c) 2006, 2007, 2008, 2009, 2011 Yubico AB
+ * Copyright (c) 2006-2012 Yubico AB
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -46,24 +46,40 @@
 #include "b64/cdecode.h"
 
 #define NONCE_LEN 32
+#define MAX_TEMPLATES 255
 
 struct ykclient_st
 {
-  CURL *curl;
+  CURLM *curl;
   const char *ca_path;
-  const char *url_template;
-  char *url;
+  size_t num_templates;
+  const char **url_templates;
+  char last_url[256];
   unsigned int client_id;
   size_t keylen;
   const char *key;
   char *key_buf;
   char *nonce;
-  char mallocd_nonce;
+  char nonce_supplied;
+  int verify_signature;
+};
+
+struct curl_data
+{
   char *curl_chunk;
   size_t curl_chunk_size;
-  int verify_signature;
 };
 
+const char *default_url_templates[] = {
+  "http://api.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+  "http://api2.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+  "http://api3.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+  "http://api4.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+  "http://api5.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";,
+};
+
+const size_t default_num_templates = 5;
+
 int
 ykclient_init (ykclient_t ** ykc)
 {
@@ -74,7 +90,9 @@
   if (!p)
     return YKCLIENT_OUT_OF_MEMORY;
 
-  p->curl = curl_easy_init ();
+  memset(p, 0, (sizeof (*p)));
+
+  p->curl = curl_multi_init ();
   if (!p->curl)
     {
       free (p);
@@ -82,36 +100,17 @@
     }
 
   p->ca_path = NULL;
-  p->url_template = NULL;
-  p->url = NULL;
-
-  p->curl_chunk = NULL;
-  p->curl_chunk_size = 0;
+  p->num_templates = 0;
+  p->url_templates = NULL;
 
   p->key = NULL;
   p->keylen = 0;
   p->key_buf = NULL;
 
-  /* Generate a random 'nonce' value */
-  {
-    int i = 0;
-    struct timeval tv;
-
-    p->nonce = malloc (NONCE_LEN + 1);
-    if (!p->nonce)
-      return YKCLIENT_OUT_OF_MEMORY;
-    p->mallocd_nonce = 1;
-
-    gettimeofday (&tv, 0);
-    srandom (tv.tv_sec * tv.tv_usec);
-
-    for (i = 0; i < NONCE_LEN; ++i)
-      {
-	p->nonce[i] = (random () % 26) + 'a';
-      }
+  memset(p->last_url, 0, sizeof(p->last_url));
 
-    p->nonce[NONCE_LEN] = 0;
-  }
+  p->nonce = NULL;
+  p->nonce_supplied = 0;
 
   /* Verification of server signature can only be done if there is
    * an API key provided
@@ -128,16 +127,14 @@
 {
   if (ykc && *ykc)
     {
-      curl_easy_cleanup ((*ykc)->curl);
-      if ((*ykc)->mallocd_nonce)
-	free ((*ykc)->nonce);
-      free ((*ykc)->url);
-      free ((*ykc)->curl_chunk);
+      curl_multi_cleanup ((*ykc)->curl);
       free ((*ykc)->key_buf);
+      free ((*ykc)->url_templates);
       free (*ykc);
     }
-  if (ykc)
+  if (ykc) {
     *ykc = NULL;
+  }
 }
 
 void
@@ -224,6 +221,8 @@
   base64_init_decodestate (&b64);
   ykc->keylen = base64_decode_block (key, key_len, ykc->key_buf, &b64);
   ykc->key = ykc->key_buf;
+  /* Now that we have a key the sensible default is to verify signatures */
+  ykc->verify_signature = 1;
 
   return YKCLIENT_OK;
 }
@@ -237,7 +236,25 @@
 void
 ykclient_set_url_template (ykclient_t * ykc, const char *url_template)
 {
-  ykc->url_template = url_template;
+  ykclient_set_url_templates (ykc, 1, (const char **) &url_template);
+}
+
+int
+ykclient_set_url_templates (ykclient_t * ykc, size_t num_templates,
+			    const char **url_templates)
+{
+  int i;
+  if(num_templates > MAX_TEMPLATES)
+    return YKCLIENT_BAD_INPUT;
+  free(ykc->url_templates);
+  ykc->url_templates = malloc(sizeof(char*) * num_templates);
+  if(!ykc->url_templates)
+    return YKCLIENT_OUT_OF_MEMORY;
+  ykc->num_templates = num_templates;
+  for(i = 0; i < num_templates; i++) {
+    ykc->url_templates[i] = url_templates[i];
+  }
+  return YKCLIENT_OK;
 }
 
 /*
@@ -249,10 +266,7 @@
 void
 ykclient_set_nonce (ykclient_t * ykc, char *nonce)
 {
-  if (ykc->mallocd_nonce)
-    free (ykc->nonce);
-  ykc->mallocd_nonce = 0;
-
+  ykc->nonce_supplied = 1;
   ykc->nonce = nonce;
 }
 
@@ -286,13 +300,6 @@
   ykclient_t *ykc;
   int ret;
 
-  /* We currently only support 0 (for default YubiCloud URL) or 1 URL argument,
-   * but this function is prepared to support all of Validation protocol 2.0,
-   * which supports multiple parallell querys to multiple validation URLs.
-   */
-  if (urlcount > 1)
-    return YKCLIENT_NOT_IMPLEMENTED;
-
   if (ykc_in == NULL)
     {
       ret = ykclient_init (&ykc);
@@ -306,8 +313,10 @@
 
   ykclient_set_client_hex (ykc, client_id, hexkey);
 
-  if (urlcount == 1)
-    ykclient_set_url_template (ykc, urls[0]);
+  if (urlcount != 0 && *urls != 0)
+    {
+      ykclient_set_url_templates(ykc, urlcount, urls);
+    }
 
   if (api_key)
     {
@@ -417,29 +426,29 @@
 const char *
 ykclient_get_last_url (ykclient_t * ykc)
 {
-  return ykc->url;
+  return ykc->last_url;
 }
 
 static size_t
 curl_callback (void *ptr, size_t size, size_t nmemb, void *data)
 {
-  ykclient_t *ykc = (ykclient_t *) data;
+  struct curl_data *curl_data = (struct curl_data*) data;
   size_t realsize = size * nmemb;
   char *p;
 
-  if (ykc->curl_chunk)
-    p = realloc (ykc->curl_chunk, ykc->curl_chunk_size + realsize + 1);
+  if (curl_data->curl_chunk)
+    p = realloc (curl_data->curl_chunk, curl_data->curl_chunk_size + realsize + 1);
   else
-    p = malloc (ykc->curl_chunk_size + realsize + 1);
+    p = malloc (curl_data->curl_chunk_size + realsize + 1);
 
   if (!p)
     return -1;
 
-  ykc->curl_chunk = p;
+  curl_data->curl_chunk = p;
 
-  memcpy (&(ykc->curl_chunk[ykc->curl_chunk_size]), ptr, realsize);
-  ykc->curl_chunk_size += realsize;
-  ykc->curl_chunk[ykc->curl_chunk_size] = 0;
+  memcpy (&(curl_data->curl_chunk[curl_data->curl_chunk_size]), ptr, realsize);
+  curl_data->curl_chunk_size += realsize;
+  curl_data->curl_chunk[curl_data->curl_chunk_size] = 0;
 
   return realsize;
 }
@@ -447,45 +456,99 @@
 int
 ykclient_request (ykclient_t * ykc, const char *yubikey)
 {
-  const char *url_template = ykc->url_template;
+  const char **url_templates = ykc->url_templates;
+  size_t num_templates = ykc->num_templates;
   char *user_agent = NULL;
   char *status;
   int out;
+  char **urls;
+  char *signature = NULL;
+  int still_running;
+  CURL **curls_list;
+  char *encoded_otp;
+  char *nonce;
 
-  if (!url_template)
-    url_template = "http://api.yubico.com/wsapi/2.0/verify?id=%d&otp=%s";;
+  if (!url_templates || *url_templates == 0) {
+    url_templates = default_url_templates;
+    num_templates = default_num_templates;
+  }
 
-  free (ykc->curl_chunk);
-  ykc->curl_chunk_size = 0;
-  ykc->curl_chunk = NULL;
+  urls = malloc(sizeof(char*) * num_templates);
+  if(!urls)
+    return YKCLIENT_OUT_OF_MEMORY;
+
+  curls_list = malloc(sizeof(CURL*) * num_templates);
+  if (!curls_list)
+    return YKCLIENT_OUT_OF_MEMORY;
+
+  memset(ykc->last_url, 0, sizeof(ykc->last_url));
 
   {
-    size_t len = strlen (url_template) + strlen (yubikey) + 20;
-    size_t wrote;
+    size_t len = strlen (PACKAGE) + 1 + strlen (PACKAGE_VERSION) + 1;
+    user_agent = malloc (len);
+    if (!user_agent)
+      return YKCLIENT_OUT_OF_MEMORY;
+    snprintf (user_agent, len, "%s/%s", PACKAGE, PACKAGE_VERSION);
+  }
 
-    free (ykc->url);
-    ykc->url = malloc (len);
-    if (!ykc->url)
+  /* URL-encode the OTP */
+  encoded_otp = curl_easy_escape(ykc->curl, yubikey, 0);
+
+  if(ykc->nonce_supplied)
+  {
+    nonce = ykc->nonce;
+  }
+  else
+  {
+    nonce = malloc (NONCE_LEN + 1);
+    if(!nonce)
       return YKCLIENT_OUT_OF_MEMORY;
-    wrote = snprintf (ykc->url, len, url_template, ykc->client_id, yubikey);
-    if (wrote < 0 || wrote > len)
-      return YKCLIENT_FORMAT_ERROR;
+
+    /* Generate a random 'nonce' value */
+    int i = 0;
+    struct timeval tv;
+
+    gettimeofday (&tv, 0);
+    srandom (tv.tv_sec * tv.tv_usec);
+
+    for (i = 0; i < NONCE_LEN; ++i)
+    {
+      nonce[i] = (random () % 26) + 'a';
+    }
+
+    nonce[NONCE_LEN] = 0;
   }
 
-  if (ykc->nonce)
+  int i = 0;
+  for(; i < num_templates; i++)
+  {
+    char *url = NULL;
+    {
+      size_t len = strlen (url_templates[i]) + strlen (encoded_otp) + 20;
+      size_t wrote;
+
+      url = malloc(len);
+      if (!url)
+	return YKCLIENT_OUT_OF_MEMORY;
+      wrote = snprintf (url, len, url_templates[i], ykc->client_id, encoded_otp);
+      if (wrote < 0 || wrote > len)
+	return YKCLIENT_FORMAT_ERROR;
+    }
+
+    if (nonce)
     {
       /* Create new URL with nonce in it. */
-      char *url, *otp_offset;
+      char *nonce_url, *otp_offset;
       size_t len;
       int wrote;
 
 #define ADD_NONCE "&nonce="
-      len = strlen (ykc->url) + strlen (ADD_NONCE) + strlen (ykc->nonce) + 1;
-      url = malloc (len);
-      if (!url)
+      len = strlen (url) + strlen (ADD_NONCE) + strlen (nonce) + 1;
+      nonce_url = malloc (len + 4); /* avoid valgrind complaint */
+      if (!nonce_url)
 	return YKCLIENT_OUT_OF_MEMORY;
 
-      /* Find the &otp= in ykc->url and insert ?nonce= before otp. Must get
+      /* Find the &otp= in url and insert ?nonce= before otp. Must get
        *  sorted headers since we calculate HMAC on the result.
        *
        * XXX this will break if the validation protocol gets a parameter that
@@ -494,229 +557,311 @@
        * Also, we assume that everyone will have at least one parameter ("id=")
        * before "otp" so there is no need to search for "?otp=".
        */
-      otp_offset = strstr (ykc->url, "&otp=");
+      otp_offset = strstr (url, "&otp=");
       if (otp_offset == NULL)
 	/* point at \0 at end of url in case there is no otp */
-	otp_offset = ykc->url + len;
+	otp_offset = url + len;
 
       /* break up ykc->url where we want to insert nonce */
       *otp_offset = 0;
 
       wrote =
-	snprintf (url, len, "%s" ADD_NONCE "%s&%s", ykc->url, ykc->nonce,
-		  otp_offset + 1);
+	snprintf (nonce_url, len, "%s" ADD_NONCE "%s&%s", url, nonce,
+	    otp_offset + 1);
       if (wrote + 1 != len)
 	return YKCLIENT_FORMAT_ERROR;
 
-      free (ykc->url);
-      ykc->url = url;
+      free (url);
+      url = nonce_url;
     }
 
-  if (ykc->key && ykc->keylen)
+    if (ykc->key && ykc->keylen)
     {
-      uint8_t digest[USHAMaxHashSize];
-      char b64dig[3 * 4 * SHA1HashSize + 1];
-      base64_encodestate b64;
-      char *text;
-      int res, res2;
-
-      /* Find parameters to sign. */
-      text = strchr (ykc->url, '?');
-      if (!text)
-	return YKCLIENT_PARSE_ERROR;
-      text++;
-
-      /* HMAC data. */
-      res = hmac (SHA1, (unsigned char *) text, strlen (text),
-		  (unsigned char *) ykc->key, ykc->keylen, digest);
-      if (res != shaSuccess)
-	return YKCLIENT_HMAC_ERROR;
-
-      /* Base64 signature. */
-      base64_init_encodestate (&b64);
-      res = base64_encode_block ((char *) digest, SHA1HashSize, b64dig, &b64);
-      res2 = base64_encode_blockend (&b64dig[res], &b64);
-      b64dig[res + res2 - 1] = '\0';
-
-      /* Escape + into %2B. */
+      if (!signature)
       {
-	char *p;
+	char b64dig[3 * 4 * SHA1HashSize + 1];
+	uint8_t digest[USHAMaxHashSize];
+	base64_encodestate b64;
+	char *text;
+	int res, res2;
+
+	/* Find parameters to sign. */
+	text = strchr (url, '?');
+	if (!text)
+	  return YKCLIENT_PARSE_ERROR;
+	text++;
+
+	/* HMAC data. */
+	res = hmac (SHA1, (unsigned char *) text, strlen (text),
+	    (unsigned char *) ykc->key, ykc->keylen, digest);
+	if (res != shaSuccess)
+	  return YKCLIENT_HMAC_ERROR;
+
+	/* Base64 signature. */
+	base64_init_encodestate (&b64);
+	res = base64_encode_block ((char *) digest, SHA1HashSize, b64dig, &b64);
+	res2 = base64_encode_blockend (&b64dig[res], &b64);
+	b64dig[res + res2 - 1] = '\0';
 
-	while ((p = strchr (b64dig, '+')))
-	  {
-	    memmove (p + 3, p + 1, strlen (p));
-	    memcpy (p, "%2B", 3);
-	  }
+	signature = curl_easy_escape(ykc->curl, b64dig, 0);
       }
 
       /* Create new URL with signature ( h= ) appended to it . */
       {
-	char *url;
+	char *sign_url;
 	size_t len;
 	int wrote;
 
+
 #define ADD_HASH "&h="
-	len = strlen (ykc->url) + strlen (ADD_HASH) + strlen (b64dig) + 1;
-	url = malloc (len);
-	if (!url)
+	len = strlen (url) + strlen (ADD_HASH) + strlen (signature) + 1;
+	sign_url = malloc (len);
+	if (!sign_url)
 	  return YKCLIENT_OUT_OF_MEMORY;
 
-	wrote = snprintf (url, len, "%s" ADD_HASH "%s", ykc->url, b64dig);
+	wrote = snprintf (sign_url, len, "%s" ADD_HASH "%s", url, signature);
 	if (wrote + 1 != len)
 	  return YKCLIENT_FORMAT_ERROR;
-	free (ykc->url);
-	ykc->url = url;
+	free (url);
+	url = sign_url;
       }
     }
 
-  if (ykc->ca_path)
     {
-      curl_easy_setopt (ykc->curl, CURLOPT_CAPATH, ykc->ca_path);
+      CURL *curl_easy = curl_easy_init();
+      struct curl_data *data = malloc(sizeof(struct curl_data));
+      if (!data) {
+	return YKCLIENT_OUT_OF_MEMORY;
+      }
+      data->curl_chunk = NULL;
+      data->curl_chunk_size = 0;
+      if (ykc->ca_path)
+      {
+	curl_easy_setopt (curl_easy, CURLOPT_CAPATH, ykc->ca_path);
+      }
+      curl_easy_setopt (curl_easy, CURLOPT_URL, url);
+      curl_easy_setopt (curl_easy, CURLOPT_WRITEFUNCTION, curl_callback);
+      curl_easy_setopt (curl_easy, CURLOPT_WRITEDATA, (void *) data);
+      curl_easy_setopt (curl_easy, CURLOPT_PRIVATE, (void *) data);
+      if(user_agent)
+      {
+	curl_easy_setopt (curl_easy, CURLOPT_USERAGENT, user_agent);
+      }
+      curl_multi_add_handle(ykc->curl, curl_easy);
+      curls_list[i] = curl_easy;
+      urls[i] = url;
     }
+  }
 
-  curl_easy_setopt (ykc->curl, CURLOPT_URL, ykc->url);
-  curl_easy_setopt (ykc->curl, CURLOPT_WRITEFUNCTION, curl_callback);
-  curl_easy_setopt (ykc->curl, CURLOPT_WRITEDATA, (void *) ykc);
+  still_running = num_templates;
+  while(still_running) {
+    CURLcode curl_ret = curl_multi_perform (ykc->curl, &still_running);
+    struct timeval timeout;
+
+    fd_set fdread;
+    fd_set fdwrite;
+    fd_set fdexcep;
+    int maxfd = -1;
 
-  {
-    size_t len = strlen (PACKAGE) + 1 + strlen (PACKAGE_VERSION) + 1;
-    user_agent = malloc (len);
-    if (!user_agent)
-      return YKCLIENT_OUT_OF_MEMORY;
-    if (snprintf (user_agent, len, "%s/%s", PACKAGE, PACKAGE_VERSION) > 0)
-      curl_easy_setopt (ykc->curl, CURLOPT_USERAGENT, user_agent);
-  }
+    long curl_timeo = -1;
 
-  CURLcode curl_ret = curl_easy_perform (ykc->curl);
+    if (curl_ret != CURLE_OK)
+      {
+	fprintf(stderr, "Error with curl: %s\n", curl_multi_strerror(curl_ret));
+	out = YKCLIENT_CURL_PERFORM_ERROR;
+	still_running = 0;
+	break;
+      }
 
-  if (curl_ret != CURLE_OK)
-    {
-      out = YKCLIENT_CURL_PERFORM_ERROR;
-      goto done;
+    FD_ZERO(&fdread);
+    FD_ZERO(&fdwrite);
+    FD_ZERO(&fdexcep);
+
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 250000;
+
+    curl_multi_timeout(ykc->curl, &curl_timeo);
+    if(curl_timeo >= 0) {
+      timeout.tv_sec = curl_timeo / 1000;
+      if(timeout.tv_sec > 1) {
+	timeout.tv_sec = 0;
+	timeout.tv_usec = 250000;
+      }
+      else
+	timeout.tv_usec = (curl_timeo % 1000) * 1000;
     }
 
-  if (ykc->curl_chunk_size == 0 || ykc->curl_chunk == NULL)
-    {
-      out = YKCLIENT_PARSE_ERROR;
-      goto done;
-    }
+    curl_multi_fdset(ykc->curl, &fdread, &fdwrite, &fdexcep, &maxfd);
 
-  ykclient_server_response_t *serv_response =
-    ykclient_server_response_init ();
-  if (serv_response == NULL)
-    {
-      out = YKCLIENT_PARSE_ERROR;
-      goto done;
-    }
+    select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
 
-  int parse_ret = ykclient_server_response_parse (ykc->curl_chunk,
-						  serv_response);
-  if (parse_ret)
     {
-      out = parse_ret;
-      goto done;
-    }
+      int msgs_left = 1;
+      while(msgs_left) {
+	CURLMsg *msg = curl_multi_info_read(ykc->curl, &msgs_left);
+	if(msg && msg->msg == CURLMSG_DONE) {
+	  CURL *curl_easy = msg->easy_handle;
+	  struct curl_data *data;
+	  char *url_used;
+	  int parse_ret;
 
-  if (ykc->verify_signature != 0 &&
-      ykclient_server_response_verify_signature (serv_response,
-						 ykc->key, ykc->keylen))
-    {
-      out = YKCLIENT_BAD_SERVER_SIGNATURE;
-      goto done;
-    }
+	  ykclient_server_response_t *serv_response = NULL;
+	  curl_easy_getinfo(curl_easy, CURLINFO_PRIVATE, (char **) &data);
 
-  status = ykclient_server_response_get (serv_response, "status");
-  if (!status)
-    {
-      out = YKCLIENT_PARSE_ERROR;
-      goto done;
-    }
+	  if (data == 0 || data->curl_chunk_size == 0 || data->curl_chunk == NULL)
+	  {
+	    out = YKCLIENT_PARSE_ERROR;
+	    goto done;
+	  }
 
-  if (strcmp (status, "OK") == 0)
-    {
-      char *server_otp;
+	  curl_easy_getinfo(curl_easy, CURLINFO_EFFECTIVE_URL, &url_used);
+	  strncpy(ykc->last_url, url_used, 200);
 
-      /* Verify that the OTP and nonce we put in our request is echoed in the response.
-       *
-       * This is to protect us from a man in the middle sending us a previously
-       * seen genuine response again (such as an status=OK response even though
-       * the real server will respond status=REPLAYED_OTP in a few milliseconds.
-       */
-      if (ykc->nonce)
-	{
-	  char *server_nonce =
-	    ykclient_server_response_get (serv_response, "nonce");
-	  if (server_nonce == NULL || strcmp (ykc->nonce, server_nonce))
+	  serv_response = ykclient_server_response_init ();
+	  if (serv_response == NULL)
+	  {
+	    out = YKCLIENT_PARSE_ERROR;
+	    goto done;
+	  }
+	  parse_ret = ykclient_server_response_parse (data->curl_chunk,
+	      serv_response);
+	  if (parse_ret)
+	  {
+	    out = parse_ret;
+	    goto done;
+	  }
+
+	  if (ykc->verify_signature != 0 &&
+	      ykclient_server_response_verify_signature (serv_response,
+		ykc->key, ykc->keylen))
+	  {
+	    out = YKCLIENT_BAD_SERVER_SIGNATURE;
+	    goto done;
+	  }
+
+	  status = ykclient_server_response_get (serv_response, "status");
+	  if (!status)
+	  {
+	    out = YKCLIENT_PARSE_ERROR;
+	    goto done;
+	  }
+
+	  if (strcmp (status, "OK") == 0)
+	  {
+	    char *server_otp;
+
+	    /* Verify that the OTP and nonce we put in our request is echoed in the response.
+	     *
+	     * This is to protect us from a man in the middle sending us a previously
+	     * seen genuine response again (such as an status=OK response even though
+	     * the real server will respond status=REPLAYED_OTP in a few milliseconds.
+	     */
+	    if (ykc->nonce)
+	    {
+	      char *server_nonce =
+		ykclient_server_response_get (serv_response, "nonce");
+	      if (server_nonce == NULL || strcmp (nonce, server_nonce))
+	      {
+		out = YKCLIENT_HMAC_ERROR;
+		goto done;
+	      }
+	    }
+
+	    server_otp = ykclient_server_response_get (serv_response, "otp");
+	    if (server_otp == NULL || strcmp (yubikey, server_otp))
 	    {
 	      out = YKCLIENT_HMAC_ERROR;
 	      goto done;
 	    }
-	}
 
-      server_otp = ykclient_server_response_get (serv_response, "otp");
-      if (server_otp == NULL || strcmp (yubikey, server_otp))
-	{
-	  out = YKCLIENT_HMAC_ERROR;
-	  goto done;
-	}
+	    out = YKCLIENT_OK;
+	    goto done;
+	  }
+	  else if (strcmp (status, "BAD_OTP") == 0)
+	  {
+	    out = YKCLIENT_BAD_OTP;
+	    goto done;
+	  }
+	  else if (strcmp (status, "REPLAYED_OTP") == 0)
+	  {
+	    out = YKCLIENT_REPLAYED_OTP;
+	    goto done;
+	  }
+	  else if (strcmp (status, "REPLAYED_REQUEST") == 0)
+	  {
+	    out = YKCLIENT_REPLAYED_REQUEST;
+	    goto done;
+	  }
+	  else if (strcmp (status, "BAD_SIGNATURE") == 0)
+	  {
+	    out = YKCLIENT_BAD_SIGNATURE;
+	    goto done;
+	  }
+	  else if (strcmp (status, "MISSING_PARAMETER") == 0)
+	  {
+	    out = YKCLIENT_MISSING_PARAMETER;
+	    goto done;
+	  }
+	  else if (strcmp (status, "NO_SUCH_CLIENT") == 0)
+	  {
+	    out = YKCLIENT_NO_SUCH_CLIENT;
+	    goto done;
+	  }
+	  else if (strcmp (status, "OPERATION_NOT_ALLOWED") == 0)
+	  {
+	    out = YKCLIENT_OPERATION_NOT_ALLOWED;
+	    goto done;
+	  }
+	  else if (strcmp (status, "BACKEND_ERROR") == 0)
+	  {
+	    out = YKCLIENT_BACKEND_ERROR;
+	    goto done;
+	  }
+	  else if (strcmp (status, "NOT_ENOUGH_ANSWERS") == 0)
+	  {
+	    out = YKCLIENT_NOT_ENOUGH_ANSWERS;
+	    goto done;
+	  }
 
-      out = YKCLIENT_OK;
-      goto done;
-    }
-  else if (strcmp (status, "BAD_OTP") == 0)
-    {
-      out = YKCLIENT_BAD_OTP;
-      goto done;
-    }
-  else if (strcmp (status, "REPLAYED_OTP") == 0)
-    {
-      out = YKCLIENT_REPLAYED_OTP;
-      goto done;
-    }
-  else if (strcmp (status, "REPLAYED_REQUEST") == 0)
-    {
-      out = YKCLIENT_REPLAYED_REQUEST;
-      goto done;
-    }
-  else if (strcmp (status, "BAD_SIGNATURE") == 0)
-    {
-      out = YKCLIENT_BAD_SIGNATURE;
-      goto done;
-    }
-  else if (strcmp (status, "MISSING_PARAMETER") == 0)
-    {
-      out = YKCLIENT_MISSING_PARAMETER;
-      goto done;
-    }
-  else if (strcmp (status, "NO_SUCH_CLIENT") == 0)
-    {
-      out = YKCLIENT_NO_SUCH_CLIENT;
-      goto done;
-    }
-  else if (strcmp (status, "OPERATION_NOT_ALLOWED") == 0)
-    {
-      out = YKCLIENT_OPERATION_NOT_ALLOWED;
-      goto done;
-    }
-  else if (strcmp (status, "BACKEND_ERROR") == 0)
-    {
-      out = YKCLIENT_BACKEND_ERROR;
-      goto done;
-    }
-  else if (strcmp (status, "NOT_ENOUGH_ANSWERS") == 0)
-    {
-      out = YKCLIENT_NOT_ENOUGH_ANSWERS;
-      goto done;
+	  out = YKCLIENT_PARSE_ERROR;
+done:
+	  if (serv_response)
+	    ykclient_server_response_free (serv_response);
+	  /* if we got a valid response, get out of the loops */
+	  if (out != YKCLIENT_PARSE_ERROR && out != YKCLIENT_REPLAYED_REQUEST)
+	  {
+	    still_running = 0;
+	    msgs_left = 0;
+	  }
+	}
+      }
     }
+  }
 
-  out = YKCLIENT_PARSE_ERROR;
+  for(i = 0; i < num_templates; i++)
+  {
+    CURL *curl = curls_list[i];
+    CURLMcode code = curl_multi_remove_handle(ykc->curl, curl);
 
-done:
-  if (user_agent)
-    free (user_agent);
+    struct curl_data *data;
+    curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **) &data);
+    free(data->curl_chunk);
+    free(data);
+
+    curl_easy_cleanup(curl);
+
+    free(urls[i]);
+  }
 
-  if (serv_response)
-    ykclient_server_response_free (serv_response);
+  /* if we allocated the nonce ourselves, free it */
+  if(!ykc->nonce_supplied)
+    free(nonce);
+
+  curl_free(encoded_otp);
+  curl_free(signature);
+  free (curls_list);
+  free(urls);
+  free (user_agent);
 
   return out;
 }
diff -Nru ykclient-2.6/ykclient.h ykclient-2.8/ykclient.h
--- ykclient-2.6/ykclient.h	2011-06-06 18:12:09.000000000 +0100
+++ ykclient-2.8/ykclient.h	2012-06-15 08:48:21.000000000 +0100
@@ -1,7 +1,7 @@
 /* ykclient.h --- Prototypes for Yubikey OTP validation client library.
  *
  * Written by Simon Josefsson <simon@josefsson.org>.
- * Copyright (c) 2006, 2007, 2008, 2009, 2011 Yubico AB
+ * Copyright (c) 2006-2012 Yubico AB
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -36,6 +36,11 @@
 #include <stdint.h>
 #include <string.h>
 
+# ifdef __cplusplus
+extern "C"
+{
+# endif
+
 typedef enum
 {
   /* Official yubikey client API errors. */
@@ -58,7 +63,8 @@
   YKCLIENT_HEX_DECODE_ERROR,
   YKCLIENT_BAD_SERVER_SIGNATURE,
   YKCLIENT_NOT_IMPLEMENTED,
-  YKCLIENT_CURL_PERFORM_ERROR
+  YKCLIENT_CURL_PERFORM_ERROR,
+  YKCLIENT_BAD_INPUT
 } ykclient_rc;
 
 typedef struct ykclient_st ykclient_t;
@@ -86,6 +92,9 @@
 extern void ykclient_set_url_template (ykclient_t * ykc,
 				       const char *url_template);
 
+extern int ykclient_set_url_templates (ykclient_t * ykc,
+				       size_t num_templates, const char **url_templates);
+
 /* By default the signature returned by the server is verified (modify
    this choice by calling ykclient_set_verify_signature()). */
 extern void ykclient_set_ca_path (ykclient_t * ykc, const char *ca_path);
@@ -114,4 +123,8 @@
 				   size_t urlcount,
 				   const char **urls, const char *api_key);
 
+# ifdef __cplusplus
+}
+# endif
+
 #endif
diff -Nru ykclient-2.6/ykclient_server_response.c ykclient-2.8/ykclient_server_response.c
--- ykclient-2.6/ykclient_server_response.c	2011-06-06 18:12:08.000000000 +0100
+++ ykclient-2.8/ykclient_server_response.c	2012-05-14 14:14:17.000000000 +0100
@@ -1,7 +1,7 @@
 /* ykclient_server_response.c --- Server response parsing and validation.
  *
  * Written by Sebastien Martini <seb@dbzteam.org>.
- * Copyright (c) 2011 Yubico AB
+ * Copyright (c) 2011-2012 Yubico AB
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -44,18 +44,6 @@
 
 /* Parameters' manipulation functions */
 
-/* Debug function. */
-static void
-parameter_print (ykclient_parameter_t * p)
-{
-  if (p == NULL)
-    return;
-  if (p->key)
-    printf ("Key: %s\n", p->key);
-  if (p->value)
-    printf ("Value: %s\n", p->value);
-}
-
 static void
 parameter_free (ykclient_parameter_t * param)
 {
@@ -70,16 +58,6 @@
   free (param);
 }
 
-/* Calls func on each parameter of params. */
-static void
-for_each_parameter (ykclient_parameters_t * params,
-		    void (*func) (ykclient_parameter_t * p))
-{
-  ykclient_parameters_t *iter = params;
-  for (; iter != NULL; iter = iter->next)
-    func (iter->parameter);
-}
-
 /* Inserts elem in front of params. */
 static void
 list_parameter_insert_front (ykclient_parameters_t ** params,
@@ -91,6 +69,8 @@
   ykclient_parameters_t *new_node = malloc (sizeof (ykclient_parameters_t));
   if (new_node == NULL)
     return;
+  memset(new_node, 0, (sizeof (ykclient_parameters_t)));
+
   new_node->parameter = elem;
   new_node->next = NULL;
 
@@ -173,6 +153,7 @@
     malloc (sizeof (ykclient_server_response_t));
   if (serv_response == NULL)
     return NULL;
+  memset(serv_response, 0, (sizeof (ykclient_server_response_t)));
   serv_response->signature = NULL;
   serv_response->parameters = NULL;
   return serv_response;
@@ -277,7 +258,7 @@
 				ykclient_server_response_t * serv_response)
 {
   if (response == NULL || serv_response == NULL)
-    return;
+    return YKCLIENT_PARSE_ERROR;
 
   trim_ws_and_lb (&response);
   while (*response != '\0')
@@ -285,6 +266,7 @@
       ykclient_parameter_t *param = malloc (sizeof (ykclient_parameter_t));
       if (param == NULL)
 	return YKCLIENT_OUT_OF_MEMORY;
+      memset(param,0, (sizeof(ykclient_parameter_t)));
       int ret = parse_next_parameter (&response, param);
       if (ret)
 	return ret;
@@ -299,7 +281,9 @@
     }
 
   /* We expect at least one parameter along its mandatory signature. */
-  if (serv_response->signature == NULL || serv_response->parameters == NULL)
+  if (serv_response->signature == NULL)
+    return YKCLIENT_BAD_SERVER_SIGNATURE;
+  if (serv_response->parameters == NULL)
     return YKCLIENT_PARSE_ERROR;
   return 0;
 }
@@ -313,7 +297,7 @@
     return 1;
 
   HMACContext ctx;
-  if (hmacReset (&ctx, SHA1, key, key_length))
+  if (hmacReset (&ctx, SHA1, (const unsigned char *) key, key_length))
     return 1;
 
   /* Iterate over parameters and feed the hmac. */
@@ -323,12 +307,13 @@
       if (hmacInput (&ctx, (unsigned char *) iter->parameter->key,
 		     strlen (iter->parameter->key)))
 	return 1;
-      if (hmacInput (&ctx, "=", 1))
+      if (hmacInput (&ctx, (const unsigned char *) "=", 1))
 	return 1;
       if (hmacInput (&ctx, (unsigned char *) iter->parameter->value,
 		     strlen (iter->parameter->value)))
 	return 1;
-      if (iter->next != NULL && hmacInput (&ctx, "&", 1))
+      if (iter->next != NULL
+	  && hmacInput (&ctx, (const unsigned char *) "&", 1))
 	return 1;
     }
 
diff -Nru ykclient-2.6/ykclient_server_response.h ykclient-2.8/ykclient_server_response.h
--- ykclient-2.6/ykclient_server_response.h	2011-06-06 18:12:09.000000000 +0100
+++ ykclient-2.8/ykclient_server_response.h	2012-05-14 14:14:04.000000000 +0100
@@ -1,7 +1,7 @@
 /* ykclient_server_response.c --- Server response parsing and validation.
  *
  * Written by Sebastien Martini <seb@dbzteam.org>.
- * Copyright (c) 2011 Yubico AB
+ * Copyright (c) 2011-2012 Yubico AB
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without

Reply to: