/* conversion.c - String conversion helper functions. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004, 2007 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H #include #endif #include #include /* Solaris 8 needs sys/types.h before time.h. */ #include #include #include #include "gpgme.h" #include "util.h" #include "debug.h" #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) /* Convert two hexadecimal digits from STR to the value they represent. Returns -1 if one of the characters is not a hexadecimal digit. */ int _gpgme_hextobyte (const char *str) { int val = 0; int i; #define NROFHEXDIGITS 2 for (i = 0; i < NROFHEXDIGITS; i++) { if (*str >= '0' && *str <= '9') val += *str - '0'; else if (*str >= 'A' && *str <= 'F') val += 10 + *str - 'A'; else if (*str >= 'a' && *str <= 'f') val += 10 + *str - 'a'; else return -1; if (i < NROFHEXDIGITS - 1) val *= 16; str++; } return val; } /* Decode the C formatted string SRC and store the result in the buffer *DESTP which is LEN bytes long. If LEN is zero, then a large enough buffer is allocated with malloc and *DESTP is set to the result. Currently, LEN is only used to specify if allocation is desired or not, the caller is expected to make sure that *DESTP is large enough if LEN is not zero. */ gpgme_error_t _gpgme_decode_c_string (const char *src, char **destp, size_t len) { char *dest; /* Set up the destination buffer. */ if (len) { if (len < strlen (src) + 1) return gpg_error (GPG_ERR_INTERNAL); dest = *destp; } else { /* The converted string will never be larger than the original string. */ dest = malloc (strlen (src) + 1); if (!dest) return gpg_error_from_errno (errno); *destp = dest; } /* Convert the string. */ while (*src) { if (*src != '\\') { *(dest++) = *(src++); continue; } switch (src[1]) { #define DECODE_ONE(match,result) \ case match: \ src += 2; \ *(dest++) = result; \ break; DECODE_ONE ('\'', '\''); DECODE_ONE ('\"', '\"'); DECODE_ONE ('\?', '\?'); DECODE_ONE ('\\', '\\'); DECODE_ONE ('a', '\a'); DECODE_ONE ('b', '\b'); DECODE_ONE ('f', '\f'); DECODE_ONE ('n', '\n'); DECODE_ONE ('r', '\r'); DECODE_ONE ('t', '\t'); DECODE_ONE ('v', '\v'); case 'x': { int val = _gpgme_hextobyte (&src[2]); if (val == -1) { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); if (*src) *(dest++) = *(src++); if (*src) *(dest++) = *(src++); } else { if (!val) { /* A binary zero is not representable in a C string. */ *(dest++) = '\\'; *(dest++) = '0'; } else *((unsigned char *) dest++) = val; src += 4; } } break; default: { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); } } } *(dest++) = 0; return 0; } /* Decode the percent escaped string SRC and store the result in the buffer *DESTP which is LEN bytes long. If LEN is zero, then a large enough buffer is allocated with malloc and *DESTP is set to the result. Currently, LEN is only used to specify if allocation is desired or not, the caller is expected to make sure that *DESTP is large enough if LEN is not zero. If BINARY is 1, then '\0' characters are allowed in the output. */ gpgme_error_t _gpgme_decode_percent_string (const char *src, char **destp, size_t len, int binary) { char *dest; /* Set up the destination buffer. */ if (len) { if (len < strlen (src) + 1) return gpg_error (GPG_ERR_INTERNAL); dest = *destp; } else { /* The converted string will never be larger than the original string. */ dest = malloc (strlen (src) + 1); if (!dest) return gpg_error_from_errno (errno); *destp = dest; } /* Convert the string. */ while (*src) { if (*src != '%') { *(dest++) = *(src++); continue; } else { int val = _gpgme_hextobyte (&src[1]); if (val == -1) { /* Should not happen. */ *(dest++) = *(src++); if (*src) *(dest++) = *(src++); if (*src) *(dest++) = *(src++); } else { if (!val && !binary) { /* A binary zero is not representable in a C string. */ *(dest++) = '\\'; *(dest++) = '0'; } else *((unsigned char *) dest++) = val; src += 3; } } } *(dest++) = 0; return 0; } /* Parse the string TIMESTAMP into a time_t. The string may either be seconds since Epoch or in the ISO 8601 format like "20390815T143012". Returns 0 for an empty string or seconds since Epoch. Leading spaces are skipped. If ENDP is not NULL, it will point to the next non-parsed character in TIMESTRING. */ time_t _gpgme_parse_timestamp (const char *timestamp, char **endp) { /* Need to skip leading spaces, because that is what strtoul does but not our ISO 8601 checking code. */ while (*timestamp && *timestamp== ' ') timestamp++; if (!*timestamp) return 0; if (strlen (timestamp) >= 15 && timestamp[8] == 'T') { struct tm buf; int year; year = atoi_4 (timestamp); if (year < 1900) return (time_t)(-1); /* Fixme: We would better use a configure test to see whether mktime can handle dates beyond 2038. */ if (sizeof (time_t) <= 4 && year >= 2038) return (time_t)2145914603; /* 2037-12-31 23:23:23 */ memset (&buf, 0, sizeof buf); buf.tm_year = year - 1900; buf.tm_mon = atoi_2 (timestamp+4) - 1; buf.tm_mday = atoi_2 (timestamp+6); buf.tm_hour = atoi_2 (timestamp+9); buf.tm_min = atoi_2 (timestamp+11); buf.tm_sec = atoi_2 (timestamp+13); if (endp) *endp = (char*)(timestamp + 15); #ifdef HAVE_TIMEGM return timegm (&buf); #else { time_t tim; putenv ("TZ=UTC"); tim = mktime (&buf); #ifdef __GNUC__ #warning fixme: we must somehow reset TZ here. It is not threadsafe anyway. #endif return tim; } #endif /* !HAVE_TIMEGM */ } else return (time_t)strtoul (timestamp, endp, 10); } static struct { char *name; gpgme_error_t err; } gnupg_errors[] = { { "EOF", GPG_ERR_EOF }, { "No_Error", GPG_ERR_NO_ERROR }, { "General_Error", GPG_ERR_GENERAL }, { "Out_Of_Core", GPG_ERR_ENOMEM }, { "Invalid_Value", GPG_ERR_INV_VALUE }, { "IO_Error", GPG_ERR_GENERAL }, { "Resource_Limit", GPG_ERR_RESOURCE_LIMIT }, { "Internal_Error", GPG_ERR_INTERNAL }, { "Bad_Certificate", GPG_ERR_BAD_CERT }, { "Bad_Certificate_Chain", GPG_ERR_BAD_CERT_CHAIN}, { "Missing_Certificate", GPG_ERR_MISSING_CERT }, { "No_Data", GPG_ERR_NO_DATA }, { "Bad_Signature", GPG_ERR_BAD_SIGNATURE }, { "Not_Implemented", GPG_ERR_NOT_IMPLEMENTED }, { "Conflict", GPG_ERR_CONFLICT }, { "Bug", GPG_ERR_BUG }, { "Read_Error", GPG_ERR_GENERAL }, { "Write_Error", GPG_ERR_GENERAL }, { "Invalid_Line", GPG_ERR_GENERAL }, { "Incomplete_Line", GPG_ERR_INCOMPLETE_LINE }, { "Invalid_Response", GPG_ERR_INV_RESPONSE }, { "Agent_Error", GPG_ERR_AGENT }, { "No_Public_Key", GPG_ERR_NO_PUBKEY }, { "No_Secret_Key", GPG_ERR_NO_SECKEY }, { "File_Open_Error", GPG_ERR_GENERAL }, { "File_Create_Error", GPG_ERR_GENERAL }, { "File_Error", GPG_ERR_GENERAL }, { "Not_Supported", GPG_ERR_NOT_SUPPORTED }, { "Invalid_Data", GPG_ERR_INV_DATA }, { "Assuan_Server_Fault", GPG_ERR_ASSUAN_SERVER_FAULT }, { "Assuan_Error", GPG_ERR_ASSUAN }, { "Invalid_Session_Key", GPG_ERR_INV_SESSION_KEY }, { "Invalid_Sexp", GPG_ERR_INV_SEXP }, { "Unsupported_Algorithm", GPG_ERR_UNSUPPORTED_ALGORITHM }, { "No_PIN_Entry", GPG_ERR_NO_PIN_ENTRY }, { "PIN_Entry_Error", GPG_ERR_NO_PIN_ENTRY }, { "Bad_PIN", GPG_ERR_BAD_PIN }, { "Bad_Passphrase", GPG_ERR_BAD_PASSPHRASE }, { "Invalid_Name", GPG_ERR_INV_NAME }, { "Bad_Public_Key", GPG_ERR_BAD_PUBKEY }, { "Bad_Secret_Key", GPG_ERR_BAD_SECKEY }, { "Bad_Data", GPG_ERR_BAD_DATA }, { "Invalid_Parameter", GPG_ERR_INV_PARAMETER }, { "Tribute_to_D_A", GPG_ERR_TRIBUTE_TO_D_A }, { "No_Dirmngr", GPG_ERR_NO_DIRMNGR }, { "Dirmngr_Error", GPG_ERR_DIRMNGR }, { "Certificate_Revoked", GPG_ERR_CERT_REVOKED }, { "No_CRL_Known", GPG_ERR_NO_CRL_KNOWN }, { "CRL_Too_Old", GPG_ERR_CRL_TOO_OLD }, { "Line_Too_Long", GPG_ERR_LINE_TOO_LONG }, { "Not_Trusted", GPG_ERR_NOT_TRUSTED }, { "Canceled", GPG_ERR_CANCELED }, { "Bad_CA_Certificate", GPG_ERR_BAD_CA_CERT }, { "Certificate_Expired", GPG_ERR_CERT_EXPIRED }, { "Certificate_Too_Young", GPG_ERR_CERT_TOO_YOUNG }, { "Unsupported_Certificate", GPG_ERR_UNSUPPORTED_CERT }, { "Unknown_Sexp", GPG_ERR_UNKNOWN_SEXP }, { "Unsupported_Protection", GPG_ERR_UNSUPPORTED_PROTECTION }, { "Corrupted_Protection", GPG_ERR_CORRUPTED_PROTECTION }, { "Ambiguous_Name", GPG_ERR_AMBIGUOUS_NAME }, { "Card_Error", GPG_ERR_CARD }, { "Card_Reset", GPG_ERR_CARD_RESET }, { "Card_Removed", GPG_ERR_CARD_REMOVED }, { "Invalid_Card", GPG_ERR_INV_CARD }, { "Card_Not_Present", GPG_ERR_CARD_NOT_PRESENT }, { "No_PKCS15_App", GPG_ERR_NO_PKCS15_APP }, { "Not_Confirmed", GPG_ERR_NOT_CONFIRMED }, { "Configuration_Error", GPG_ERR_CONFIGURATION }, { "No_Policy_Match", GPG_ERR_NO_POLICY_MATCH }, { "Invalid_Index", GPG_ERR_INV_INDEX }, { "Invalid_Id", GPG_ERR_INV_ID }, { "No_Scdaemon", GPG_ERR_NO_SCDAEMON }, { "Scdaemon_Error", GPG_ERR_SCDAEMON }, { "Unsupported_Protocol", GPG_ERR_UNSUPPORTED_PROTOCOL }, { "Bad_PIN_Method", GPG_ERR_BAD_PIN_METHOD }, { "Card_Not_Initialized", GPG_ERR_CARD_NOT_INITIALIZED }, { "Unsupported_Operation", GPG_ERR_UNSUPPORTED_OPERATION }, { "Wrong_Key_Usage", GPG_ERR_WRONG_KEY_USAGE } }; gpgme_error_t _gpgme_map_gnupg_error (char *errstr) { unsigned int i; gpgme_error_t err = gpg_err_make (GPG_ERR_SOURCE_GPG, GPG_ERR_GENERAL); /* Future version of GnuPG might return the error code directly, so we first test for a a numerical value and use that verbatim. Note that this numerical value might be followed by an underschore and the textual representation of the error code. */ if (*errstr >= '0' && *errstr <= '9') return strtoul (errstr, NULL, 10); /* Well, this is a token, use the mapping table to get the error. The drawback is that we won't receive an error source and have to use GPG as source. */ for (i = 0; i < DIM (gnupg_errors); i++) if (!strcmp (gnupg_errors[i].name, errstr)) err = gpg_err_make (GPG_ERR_SOURCE_GPG, gnupg_errors[i].err); TRACE3 (DEBUG_CTX, "_gpgme_map_gnupg_error", 0, "mapped %s to %s <%s>", errstr, gpgme_strerror (err), gpgme_strsource (err)); return err; }