do_change_pin (app_t app, ctrl_t ctrl,  const char *chvnostr,
               unsigned int flags,
               gpg_error_t (*pincb)(void*, const char *, char **),
               void *pincb_arg)
{
  int rc = 0;
  int chvno = atoi (chvnostr);
  char *resetcode = NULL;
  char *oldpinvalue = NULL;
  char *pinvalue = NULL;
  int reset_mode = !!(flags & APP_CHANGE_FLAG_RESET);
  int set_resetcode = 0;
  pininfo_t pininfo;
  int use_pinpad = 0;
  int minlen = 6;

  (void)ctrl;
  memset (&pininfo, 0, sizeof pininfo);
  pininfo.fixedlen = -1;
  pininfo.minlen = minlen;

  if (reset_mode && chvno == 3)
    {
      rc = gpg_error (GPG_ERR_INV_ID);
      goto leave;
    }

  if (!app->app_local->extcap.is_v2)
    {
      /* Version 1 cards.  */

      if (reset_mode || chvno == 3)
        {
          /* We always require that the PIN is entered. */
          app->did_chv3 = 0;
          rc = verify_chv3 (app, pincb, pincb_arg);
          if (rc)
            goto leave;
        }
      else if (chvno == 1 || chvno == 2)
        {
          /* On a v1.x card CHV1 and CVH2 should always have the same
             value, thus we enforce it here.  */
          int save_force = app->force_chv1;

          app->force_chv1 = 0;
          app->did_chv1 = 0;
          app->did_chv2 = 0;
          rc = verify_chv2 (app, pincb, pincb_arg);
          app->force_chv1 = save_force;
          if (rc)
            goto leave;
        }
      else
        {
          rc = gpg_error (GPG_ERR_INV_ID);
          goto leave;
        }
    }
  else
    {
      /* Version 2 cards.  */

      if (!opt.disable_pinpad
          && !iso7816_check_pinpad (app->slot,
                                    ISO7816_CHANGE_REFERENCE_DATA, &pininfo)
          && !check_pinpad_request (app, &pininfo, chvno == 3))
        use_pinpad = 1;

      if (reset_mode)
        {
          /* To reset a PIN the Admin PIN is required. */
          use_pinpad = 0;
          app->did_chv3 = 0;
          rc = verify_chv3 (app, pincb, pincb_arg);
          if (rc)
            goto leave;

          if (chvno == 2)
            set_resetcode = 1;
        }
      else if (chvno == 1 || chvno == 3)
        {
	  if (!use_pinpad)
            {
              char *promptbuf = NULL;
              const char *prompt;

              if (chvno == 3)
                {
                  minlen = 8;
                  rc = build_enter_admin_pin_prompt (app, &promptbuf);
                  if (rc)
                    goto leave;
                  prompt = promptbuf;
                }
              else
                prompt = _("||Please enter the PIN");
              rc = pincb (pincb_arg, prompt, &oldpinvalue);
              xfree (promptbuf);
              promptbuf = NULL;
              if (rc)
                {
                  log_info (_("PIN callback returned error: %s\n"),
                            gpg_strerror (rc));
                  goto leave;
                }

              if (strlen (oldpinvalue) < minlen)
                {
                  log_info (_("PIN for CHV%d is too short;"
                              " minimum length is %d\n"), chvno, minlen);
                  rc = gpg_error (GPG_ERR_BAD_PIN);
                  goto leave;
                }
            }
        }
      else if (chvno == 2)
        {
          /* There is no PW2 for v2 cards.  We use this condition to
             allow a PW reset using the Reset Code.  */
          void *relptr;
          unsigned char *value;
          size_t valuelen;
          int remaining;

          use_pinpad = 0;
          minlen = 8;
          relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
          if (!relptr || valuelen < 7)
            {
              log_error (_("error retrieving CHV status from card\n"));
              xfree (relptr);
              rc = gpg_error (GPG_ERR_CARD);
              goto leave;
            }
          remaining = value[5];
          xfree (relptr);
          if (!remaining)
            {
              log_error (_("Reset Code not or not anymore available\n"));
              rc = gpg_error (GPG_ERR_BAD_PIN);
              goto leave;
            }

          rc = pincb (pincb_arg,
                      _("||Please enter the Reset Code for the card"),
                      &resetcode);
          if (rc)
            {
              log_info (_("PIN callback returned error: %s\n"),
                        gpg_strerror (rc));
              goto leave;
            }
          if (strlen (resetcode) < minlen)
            {
              log_info (_("Reset Code is too short; minimum length is %d\n"),
                        minlen);
              rc = gpg_error (GPG_ERR_BAD_PIN);
              goto leave;
            }
        }
      else
        {
          rc = gpg_error (GPG_ERR_INV_ID);
          goto leave;
        }
    }

  if (chvno == 3)
    app->did_chv3 = 0;
  else
    app->did_chv1 = app->did_chv2 = 0;

  if (!use_pinpad)
    {
      /* TRANSLATORS: Do not translate the "|*|" prefixes but
         keep it at the start of the string.  We need this elsewhere
         to get some infos on the string. */
      rc = pincb (pincb_arg, set_resetcode? _("|RN|New Reset Code") :
                  chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"),
                  &pinvalue);
      if (rc)
        {
          log_error (_("error getting new PIN: %s\n"), gpg_strerror (rc));
          goto leave;
        }
    }


  if (resetcode)
    {
      char *buffer;

      buffer = xtrymalloc (strlen (resetcode) + strlen (pinvalue) + 1);
      if (!buffer)
        rc = gpg_error_from_syserror ();
      else
        {
          strcpy (stpcpy (buffer, resetcode), pinvalue);
          rc = iso7816_reset_retry_counter_with_rc (app->slot, 0x81,
                                                    buffer, strlen (buffer));
          wipememory (buffer, strlen (buffer));
          xfree (buffer);
        }
    }
  else if (set_resetcode)
    {
      if (strlen (pinvalue) < 8)
        {
          log_error (_("Reset Code is too short; minimum length is %d\n"), 8);
          rc = gpg_error (GPG_ERR_BAD_PIN);
        }
      else
        rc = iso7816_put_data (app->slot, 0, 0xD3,
                               pinvalue, strlen (pinvalue));
    }
  else if (reset_mode)
    {
      rc = iso7816_reset_retry_counter (app->slot, 0x81,
                                        pinvalue, strlen (pinvalue));
      if (!rc && !app->app_local->extcap.is_v2)
        rc = iso7816_reset_retry_counter (app->slot, 0x82,
                                          pinvalue, strlen (pinvalue));
    }
  else if (!app->app_local->extcap.is_v2)
    {
      /* Version 1 cards.  */
      if (chvno == 1 || chvno == 2)
        {
          rc = iso7816_change_reference_data (app->slot, 0x81, NULL, 0,
                                              pinvalue, strlen (pinvalue));
          if (!rc)
            rc = iso7816_change_reference_data (app->slot, 0x82, NULL, 0,
                                                pinvalue, strlen (pinvalue));
        }
      else /* CHVNO == 3 */
        {
          rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, NULL, 0,
                                              pinvalue, strlen (pinvalue));
        }
    }
  else
    {
      /* Version 2 cards.  */
      assert (chvno == 1 || chvno == 3);

      if (use_pinpad)
        {
          rc = pincb (pincb_arg,
                      chvno == 3 ?
                      _("||Please enter the Admin PIN and New Admin PIN") :
                      _("||Please enter the PIN and New PIN"), NULL);
          if (rc)
            {
              log_info (_("PIN callback returned error: %s\n"),
                        gpg_strerror (rc));
              goto leave;
            }
          rc = iso7816_change_reference_data_kp (app->slot, 0x80 + chvno, 0,
                                                 &pininfo);
          pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
        }
      else
        rc = iso7816_change_reference_data (app->slot, 0x80 + chvno,
                                            oldpinvalue, strlen (oldpinvalue),
                                            pinvalue, strlen (pinvalue));
    }

  if (pinvalue)
    {
      wipememory (pinvalue, strlen (pinvalue));
      xfree (pinvalue);
    }
  if (rc)
    flush_cache_after_error (app);

 leave:
  if (resetcode)
    {
      wipememory (resetcode, strlen (resetcode));
      xfree (resetcode);
    }
  if (oldpinvalue)
    {
      wipememory (oldpinvalue, strlen (oldpinvalue));
      xfree (oldpinvalue);
    }
  return rc;
}