/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include "FwdLockConv.h" #include "FwdLockGlue.h" #define TRUE 1 #define FALSE 0 #define INVALID_OFFSET ((off64_t)-1) #define MAX_NUM_SESSIONS 32 #define OUTPUT_BUFFER_SIZE_INCREMENT 1024 #define READ_BUFFER_SIZE 1024 #define MAX_BOUNDARY_LENGTH 70 #define MAX_DELIMITER_LENGTH (MAX_BOUNDARY_LENGTH + 4) #define STRING_LENGTH_INCREMENT 25 #define KEY_SIZE AES_BLOCK_SIZE #define KEY_SIZE_IN_BITS (KEY_SIZE * 8) #define SHA1_HASH_SIZE 20 #define FWD_LOCK_VERSION 0 #define FWD_LOCK_SUBFORMAT 0 #define USAGE_RESTRICTION_FLAGS 0 #define CONTENT_TYPE_LENGTH_POS 7 #define TOP_HEADER_SIZE 8 /** * Data type for the parser states of the converter. */ typedef enum FwdLockConv_ParserState { FwdLockConv_ParserState_WantsOpenDelimiter, FwdLockConv_ParserState_WantsMimeHeaders, FwdLockConv_ParserState_WantsBinaryEncodedData, FwdLockConv_ParserState_WantsBase64EncodedData, FwdLockConv_ParserState_Done } FwdLockConv_ParserState_t; /** * Data type for the scanner states of the converter. */ typedef enum FwdLockConv_ScannerState { FwdLockConv_ScannerState_WantsFirstDash, FwdLockConv_ScannerState_WantsSecondDash, FwdLockConv_ScannerState_WantsCR, FwdLockConv_ScannerState_WantsLF, FwdLockConv_ScannerState_WantsBoundary, FwdLockConv_ScannerState_WantsBoundaryEnd, FwdLockConv_ScannerState_WantsMimeHeaderNameStart, FwdLockConv_ScannerState_WantsMimeHeaderName, FwdLockConv_ScannerState_WantsMimeHeaderNameEnd, FwdLockConv_ScannerState_WantsContentTypeStart, FwdLockConv_ScannerState_WantsContentType, FwdLockConv_ScannerState_WantsContentTransferEncodingStart, FwdLockConv_ScannerState_Wants_A_OR_I, FwdLockConv_ScannerState_Wants_N, FwdLockConv_ScannerState_Wants_A, FwdLockConv_ScannerState_Wants_R, FwdLockConv_ScannerState_Wants_Y, FwdLockConv_ScannerState_Wants_S, FwdLockConv_ScannerState_Wants_E, FwdLockConv_ScannerState_Wants_6, FwdLockConv_ScannerState_Wants_4, FwdLockConv_ScannerState_Wants_B, FwdLockConv_ScannerState_Wants_I, FwdLockConv_ScannerState_Wants_T, FwdLockConv_ScannerState_WantsContentTransferEncodingEnd, FwdLockConv_ScannerState_WantsMimeHeaderValueEnd, FwdLockConv_ScannerState_WantsMimeHeadersEnd, FwdLockConv_ScannerState_WantsByte1, FwdLockConv_ScannerState_WantsByte1_AfterCRLF, FwdLockConv_ScannerState_WantsByte2, FwdLockConv_ScannerState_WantsByte3, FwdLockConv_ScannerState_WantsByte4, FwdLockConv_ScannerState_WantsPadding, FwdLockConv_ScannerState_WantsWhitespace, FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF, FwdLockConv_ScannerState_WantsDelimiter } FwdLockConv_ScannerState_t; /** * Data type for the content transfer encoding. */ typedef enum FwdLockConv_ContentTransferEncoding { FwdLockConv_ContentTransferEncoding_Undefined, FwdLockConv_ContentTransferEncoding_Binary, FwdLockConv_ContentTransferEncoding_Base64 } FwdLockConv_ContentTransferEncoding_t; /** * Data type for a dynamically growing string. */ typedef struct FwdLockConv_String { char *ptr; size_t length; size_t maxLength; size_t lengthIncrement; } FwdLockConv_String_t; /** * Data type for the per-file state information needed by the converter. */ typedef struct FwdLockConv_Session { FwdLockConv_ParserState_t parserState; FwdLockConv_ScannerState_t scannerState; FwdLockConv_ScannerState_t savedScannerState; off64_t numCharsConsumed; char delimiter[MAX_DELIMITER_LENGTH]; size_t delimiterLength; size_t delimiterMatchPos; FwdLockConv_String_t mimeHeaderName; FwdLockConv_String_t contentType; FwdLockConv_ContentTransferEncoding_t contentTransferEncoding; unsigned char sessionKey[KEY_SIZE]; void *pEncryptedSessionKey; size_t encryptedSessionKeyLength; AES_KEY encryptionRoundKeys; HMAC_CTX signingContext; unsigned char topHeader[TOP_HEADER_SIZE]; unsigned char counter[AES_BLOCK_SIZE]; unsigned char keyStream[AES_BLOCK_SIZE]; int keyStreamIndex; unsigned char ch; size_t outputBufferSize; size_t dataOffset; size_t numDataBytes; } FwdLockConv_Session_t; static FwdLockConv_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL }; static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER; static const FwdLockConv_String_t nullString = { NULL, 0, 0, STRING_LENGTH_INCREMENT }; static const unsigned char topHeaderTemplate[] = { 'F', 'W', 'L', 'K', FWD_LOCK_VERSION, FWD_LOCK_SUBFORMAT, USAGE_RESTRICTION_FLAGS }; static const char strContent[] = "content-"; static const char strType[] = "type"; static const char strTransferEncoding[] = "transfer-encoding"; static const char strTextPlain[] = "text/plain"; static const char strApplicationVndOmaDrmRightsXml[] = "application/vnd.oma.drm.rights+xml"; static const char strApplicationVndOmaDrmContent[] = "application/vnd.oma.drm.content"; static const size_t strlenContent = sizeof strContent - 1; static const size_t strlenTextPlain = sizeof strTextPlain - 1; static const signed char base64Values[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 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 }; /** * Acquires an unused converter session. * * @return A session ID. */ static int FwdLockConv_AcquireSession() { int sessionId = -1; int i; pthread_mutex_lock(&sessionAcquisitionMutex); for (i = 0; i < MAX_NUM_SESSIONS; ++i) { if (sessionPtrs[i] == NULL) { sessionPtrs[i] = malloc(sizeof *sessionPtrs[i]); if (sessionPtrs[i] != NULL) { sessionId = i; } break; } } pthread_mutex_unlock(&sessionAcquisitionMutex); return sessionId; } /** * Checks whether a session ID is in range and currently in use. * * @param[in] sessionID A session ID. * * @return A Boolean value indicating whether the session ID is in range and currently in use. */ static int FwdLockConv_IsValidSession(int sessionId) { return 0 <= sessionId && sessionId < MAX_NUM_SESSIONS && sessionPtrs[sessionId] != NULL; } /** * Releases a converter session. * * @param[in] sessionID A session ID. */ static void FwdLockConv_ReleaseSession(int sessionId) { pthread_mutex_lock(&sessionAcquisitionMutex); assert(FwdLockConv_IsValidSession(sessionId)); memset(sessionPtrs[sessionId], 0, sizeof *sessionPtrs[sessionId]); // Zero out key data. free(sessionPtrs[sessionId]); sessionPtrs[sessionId] = NULL; pthread_mutex_unlock(&sessionAcquisitionMutex); } /** * Derives cryptographically independent keys for encryption and signing from the session key. * * @param[in,out] pSession A reference to a converter session. * * @return A status code. */ static int FwdLockConv_DeriveKeys(FwdLockConv_Session_t *pSession) { FwdLockConv_Status_t status; struct FwdLockConv_DeriveKeys_Data { AES_KEY sessionRoundKeys; unsigned char value[KEY_SIZE]; unsigned char key[KEY_SIZE]; }; const size_t kSize = sizeof(struct FwdLockConv_DeriveKeys_Data); struct FwdLockConv_DeriveKeys_Data *pData = malloc(kSize); if (pData == NULL) { status = FwdLockConv_Status_OutOfMemory; } else { if (AES_set_encrypt_key(pSession->sessionKey, KEY_SIZE_IN_BITS, &pData->sessionRoundKeys) != 0) { status = FwdLockConv_Status_ProgramError; } else { // Encrypt the 16-byte value {0, 0, ..., 0} to produce the encryption key. memset(pData->value, 0, KEY_SIZE); AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys); if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS, &pSession->encryptionRoundKeys) != 0) { status = FwdLockConv_Status_ProgramError; } else { // Encrypt the 16-byte value {1, 0, ..., 0} to produce the signing key. ++pData->value[0]; AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys); HMAC_CTX_init(&pSession->signingContext); HMAC_Init_ex(&pSession->signingContext, pData->key, KEY_SIZE, EVP_sha1(), NULL); status = FwdLockConv_Status_OK; } } memset(pData, 0, kSize); // Zero out key data. free(pData); } return status; } /** * Checks whether a given character is valid in a boundary. Allows some non-standard characters that * are invalid according to RFC 2046 but nevertheless used by one vendor's DRM packager. Note that * the boundary may contain leading and internal spaces. * * @param[in] ch The character to check. * * @return A Boolean value indicating whether the given character is valid in a boundary. */ static int FwdLockConv_IsBoundaryChar(int ch) { return isalnum(ch) || ch == '\'' || ch == '(' || ch == ')' || ch == '+' || ch == '_' || ch == ',' || ch == '-' || ch == '.' || ch == '/' || ch == ':' || ch == '=' || ch == '?' || ch == ' ' || ch == '%' || ch == '[' || ch == '&' || ch == '*' || ch == '^'; } /** * Checks whether a given character should be considered whitespace, using a narrower definition * than the standard-library isspace() function. * * @param[in] ch The character to check. * * @return A Boolean value indicating whether the given character should be considered whitespace. */ static int FwdLockConv_IsWhitespace(int ch) { return ch == ' ' || ch == '\t'; } /** * Removes trailing spaces from the delimiter. * * @param[in,out] pSession A reference to a converter session. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_RightTrimDelimiter(FwdLockConv_Session_t *pSession) { while (pSession->delimiterLength > 4 && pSession->delimiter[pSession->delimiterLength - 1] == ' ') { --pSession->delimiterLength; } if (pSession->delimiterLength > 4) { return FwdLockConv_Status_OK; } return FwdLockConv_Status_SyntaxError; } /** * Matches the open delimiter. * * @param[in,out] pSession A reference to a converter session. * @param[in] ch A character. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_MatchOpenDelimiter(FwdLockConv_Session_t *pSession, int ch) { FwdLockConv_Status_t status = FwdLockConv_Status_OK; switch (pSession->scannerState) { case FwdLockConv_ScannerState_WantsFirstDash: if (ch == '-') { pSession->scannerState = FwdLockConv_ScannerState_WantsSecondDash; } else if (ch == '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else { pSession->scannerState = FwdLockConv_ScannerState_WantsCR; } break; case FwdLockConv_ScannerState_WantsSecondDash: if (ch == '-') { // The delimiter starts with "\r\n--" (the open delimiter may omit the initial "\r\n"). // The rest is the user-defined boundary that should come next. pSession->delimiter[0] = '\r'; pSession->delimiter[1] = '\n'; pSession->delimiter[2] = '-'; pSession->delimiter[3] = '-'; pSession->delimiterLength = 4; pSession->scannerState = FwdLockConv_ScannerState_WantsBoundary; } else if (ch == '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else { pSession->scannerState = FwdLockConv_ScannerState_WantsCR; } break; case FwdLockConv_ScannerState_WantsCR: if (ch == '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } break; case FwdLockConv_ScannerState_WantsLF: if (ch == '\n') { pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash; } else if (ch != '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsCR; } break; case FwdLockConv_ScannerState_WantsBoundary: if (FwdLockConv_IsBoundaryChar(ch)) { // The boundary may contain leading and internal spaces, so trailing spaces will also be // matched here. These will be removed later. if (pSession->delimiterLength < MAX_DELIMITER_LENGTH) { pSession->delimiter[pSession->delimiterLength++] = ch; } else if (ch != ' ') { status = FwdLockConv_Status_SyntaxError; } } else if (ch == '\r') { status = FwdLockConv_RightTrimDelimiter(pSession); if (status == FwdLockConv_Status_OK) { pSession->scannerState = FwdLockConv_ScannerState_WantsBoundaryEnd; } } else if (ch == '\t') { status = FwdLockConv_RightTrimDelimiter(pSession); if (status == FwdLockConv_Status_OK) { pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace; } } else { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsWhitespace: if (ch == '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsBoundaryEnd; } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsBoundaryEnd: if (ch == '\n') { pSession->parserState = FwdLockConv_ParserState_WantsMimeHeaders; pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderNameStart; } else { status = FwdLockConv_Status_SyntaxError; } break; default: status = FwdLockConv_Status_ProgramError; break; } return status; } /** * Checks whether a given character is valid in a MIME header name. * * @param[in] ch The character to check. * * @return A Boolean value indicating whether the given character is valid in a MIME header name. */ static int FwdLockConv_IsMimeHeaderNameChar(int ch) { return isgraph(ch) && ch != ':'; } /** * Checks whether a given character is valid in a MIME header value. * * @param[in] ch The character to check. * * @return A Boolean value indicating whether the given character is valid in a MIME header value. */ static int FwdLockConv_IsMimeHeaderValueChar(int ch) { return isgraph(ch) && ch != ';'; } /** * Appends a character to the specified dynamically growing string. * * @param[in,out] pString A reference to a dynamically growing string. * @param[in] ch The character to append. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_StringAppend(FwdLockConv_String_t *pString, int ch) { if (pString->length == pString->maxLength) { size_t newMaxLength = pString->maxLength + pString->lengthIncrement; char *newPtr = realloc(pString->ptr, newMaxLength + 1); if (newPtr == NULL) { return FwdLockConv_Status_OutOfMemory; } pString->ptr = newPtr; pString->maxLength = newMaxLength; } pString->ptr[pString->length++] = ch; pString->ptr[pString->length] = '\0'; return FwdLockConv_Status_OK; } /** * Attempts to recognize the MIME header name and changes the scanner state accordingly. * * @param[in,out] pSession A reference to a converter session. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_RecognizeMimeHeaderName(FwdLockConv_Session_t *pSession) { FwdLockConv_Status_t status = FwdLockConv_Status_OK; if (strncmp(pSession->mimeHeaderName.ptr, strContent, strlenContent) == 0) { if (strcmp(pSession->mimeHeaderName.ptr + strlenContent, strType) == 0) { if (pSession->contentType.ptr == NULL) { pSession->scannerState = FwdLockConv_ScannerState_WantsContentTypeStart; } else { status = FwdLockConv_Status_SyntaxError; } } else if (strcmp(pSession->mimeHeaderName.ptr + strlenContent, strTransferEncoding) == 0) { if (pSession->contentTransferEncoding == FwdLockConv_ContentTransferEncoding_Undefined) { pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingStart; } else { status = FwdLockConv_Status_SyntaxError; } } else { pSession->scannerState = FwdLockConv_ScannerState_WantsCR; } } else { pSession->scannerState = FwdLockConv_ScannerState_WantsCR; } return status; } /** * Applies defaults to missing MIME header values. * * @param[in,out] pSession A reference to a converter session. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_ApplyDefaults(FwdLockConv_Session_t *pSession) { if (pSession->contentType.ptr == NULL) { // Content type is missing: default to "text/plain". pSession->contentType.ptr = malloc(sizeof strTextPlain); if (pSession->contentType.ptr == NULL) { return FwdLockConv_Status_OutOfMemory; } memcpy(pSession->contentType.ptr, strTextPlain, sizeof strTextPlain); pSession->contentType.length = strlenTextPlain; pSession->contentType.maxLength = strlenTextPlain; } if (pSession->contentTransferEncoding == FwdLockConv_ContentTransferEncoding_Undefined) { // Content transfer encoding is missing: default to binary. pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Binary; } return FwdLockConv_Status_OK; } /** * Verifies that the content type is supported. * * @param[in,out] pSession A reference to a converter session. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_VerifyContentType(FwdLockConv_Session_t *pSession) { FwdLockConv_Status_t status; if (pSession->contentType.ptr == NULL) { status = FwdLockConv_Status_ProgramError; } else if (strcmp(pSession->contentType.ptr, strApplicationVndOmaDrmRightsXml) == 0 || strcmp(pSession->contentType.ptr, strApplicationVndOmaDrmContent) == 0) { status = FwdLockConv_Status_UnsupportedFileFormat; } else { status = FwdLockConv_Status_OK; } return status; } /** * Writes the header of the output file. * * @param[in,out] pSession A reference to a converter session. * @param[out] pOutput The output from the conversion process. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_WriteHeader(FwdLockConv_Session_t *pSession, FwdLockConv_Output_t *pOutput) { FwdLockConv_Status_t status; if (pSession->contentType.length > UCHAR_MAX) { status = FwdLockConv_Status_SyntaxError; } else { pSession->outputBufferSize = OUTPUT_BUFFER_SIZE_INCREMENT; pOutput->fromConvertData.pBuffer = malloc(pSession->outputBufferSize); if (pOutput->fromConvertData.pBuffer == NULL) { status = FwdLockConv_Status_OutOfMemory; } else { size_t encryptedSessionKeyPos = TOP_HEADER_SIZE + pSession->contentType.length; size_t dataSignaturePos = encryptedSessionKeyPos + pSession->encryptedSessionKeyLength; size_t headerSignaturePos = dataSignaturePos + SHA1_HASH_SIZE; pSession->dataOffset = headerSignaturePos + SHA1_HASH_SIZE; memcpy(pSession->topHeader, topHeaderTemplate, sizeof topHeaderTemplate); pSession->topHeader[CONTENT_TYPE_LENGTH_POS] = (unsigned char)pSession->contentType.length; memcpy(pOutput->fromConvertData.pBuffer, pSession->topHeader, TOP_HEADER_SIZE); memcpy((char *)pOutput->fromConvertData.pBuffer + TOP_HEADER_SIZE, pSession->contentType.ptr, pSession->contentType.length); memcpy((char *)pOutput->fromConvertData.pBuffer + encryptedSessionKeyPos, pSession->pEncryptedSessionKey, pSession->encryptedSessionKeyLength); // Set the signatures to all zeros for now; they will have to be updated later. memset((char *)pOutput->fromConvertData.pBuffer + dataSignaturePos, 0, SHA1_HASH_SIZE); memset((char *)pOutput->fromConvertData.pBuffer + headerSignaturePos, 0, SHA1_HASH_SIZE); pOutput->fromConvertData.numBytes = pSession->dataOffset; status = FwdLockConv_Status_OK; } } return status; } /** * Matches the MIME headers. * * @param[in,out] pSession A reference to a converter session. * @param[in] ch A character. * @param[out] pOutput The output from the conversion process. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_MatchMimeHeaders(FwdLockConv_Session_t *pSession, int ch, FwdLockConv_Output_t *pOutput) { FwdLockConv_Status_t status = FwdLockConv_Status_OK; switch (pSession->scannerState) { case FwdLockConv_ScannerState_WantsMimeHeaderNameStart: if (FwdLockConv_IsMimeHeaderNameChar(ch)) { pSession->mimeHeaderName.length = 0; status = FwdLockConv_StringAppend(&pSession->mimeHeaderName, tolower(ch)); if (status == FwdLockConv_Status_OK) { pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderName; } } else if (ch == '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeadersEnd; } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsMimeHeaderName: if (FwdLockConv_IsMimeHeaderNameChar(ch)) { status = FwdLockConv_StringAppend(&pSession->mimeHeaderName, tolower(ch)); } else if (ch == ':') { status = FwdLockConv_RecognizeMimeHeaderName(pSession); } else if (FwdLockConv_IsWhitespace(ch)) { pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderNameEnd; } else { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsMimeHeaderNameEnd: if (ch == ':') { status = FwdLockConv_RecognizeMimeHeaderName(pSession); } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsContentTypeStart: if (FwdLockConv_IsMimeHeaderValueChar(ch)) { status = FwdLockConv_StringAppend(&pSession->contentType, tolower(ch)); if (status == FwdLockConv_Status_OK) { pSession->scannerState = FwdLockConv_ScannerState_WantsContentType; } } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsContentType: if (FwdLockConv_IsMimeHeaderValueChar(ch)) { status = FwdLockConv_StringAppend(&pSession->contentType, tolower(ch)); } else if (ch == ';') { pSession->scannerState = FwdLockConv_ScannerState_WantsCR; } else if (ch == '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else if (FwdLockConv_IsWhitespace(ch)) { pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderValueEnd; } else { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsContentTransferEncodingStart: if (ch == 'b' || ch == 'B') { pSession->scannerState = FwdLockConv_ScannerState_Wants_A_OR_I; } else if (ch == '7' || ch == '8') { pSession->scannerState = FwdLockConv_ScannerState_Wants_B; } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_A_OR_I: if (ch == 'i' || ch == 'I') { pSession->scannerState = FwdLockConv_ScannerState_Wants_N; } else if (ch == 'a' || ch == 'A') { pSession->scannerState = FwdLockConv_ScannerState_Wants_S; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_N: if (ch == 'n' || ch == 'N') { pSession->scannerState = FwdLockConv_ScannerState_Wants_A; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_A: if (ch == 'a' || ch == 'A') { pSession->scannerState = FwdLockConv_ScannerState_Wants_R; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_R: if (ch == 'r' || ch == 'R') { pSession->scannerState = FwdLockConv_ScannerState_Wants_Y; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_Y: if (ch == 'y' || ch == 'Y') { pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Binary; pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingEnd; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_S: if (ch == 's' || ch == 'S') { pSession->scannerState = FwdLockConv_ScannerState_Wants_E; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_E: if (ch == 'e' || ch == 'E') { pSession->scannerState = FwdLockConv_ScannerState_Wants_6; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_6: if (ch == '6') { pSession->scannerState = FwdLockConv_ScannerState_Wants_4; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_4: if (ch == '4') { pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Base64; pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingEnd; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_B: if (ch == 'b' || ch == 'B') { pSession->scannerState = FwdLockConv_ScannerState_Wants_I; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_I: if (ch == 'i' || ch == 'I') { pSession->scannerState = FwdLockConv_ScannerState_Wants_T; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_Wants_T: if (ch == 't' || ch == 'T') { pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Binary; pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingEnd; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_WantsContentTransferEncodingEnd: if (ch == ';') { pSession->scannerState = FwdLockConv_ScannerState_WantsCR; } else if (ch == '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else if (FwdLockConv_IsWhitespace(ch)) { pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderValueEnd; } else { status = FwdLockConv_Status_UnsupportedContentTransferEncoding; } break; case FwdLockConv_ScannerState_WantsMimeHeaderValueEnd: if (ch == ';') { pSession->scannerState = FwdLockConv_ScannerState_WantsCR; } else if (ch == '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsCR: if (ch == '\r') { pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } break; case FwdLockConv_ScannerState_WantsLF: if (ch == '\n') { pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderNameStart; } else { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsMimeHeadersEnd: if (ch == '\n') { status = FwdLockConv_ApplyDefaults(pSession); if (status == FwdLockConv_Status_OK) { status = FwdLockConv_VerifyContentType(pSession); } if (status == FwdLockConv_Status_OK) { status = FwdLockConv_WriteHeader(pSession, pOutput); } if (status == FwdLockConv_Status_OK) { if (pSession->contentTransferEncoding == FwdLockConv_ContentTransferEncoding_Binary) { pSession->parserState = FwdLockConv_ParserState_WantsBinaryEncodedData; } else { pSession->parserState = FwdLockConv_ParserState_WantsBase64EncodedData; } pSession->scannerState = FwdLockConv_ScannerState_WantsByte1; } } else { status = FwdLockConv_Status_SyntaxError; } break; default: status = FwdLockConv_Status_ProgramError; break; } return status; } /** * Increments the counter, treated as a 16-byte little-endian number, by one. * * @param[in,out] pSession A reference to a converter session. */ static void FwdLockConv_IncrementCounter(FwdLockConv_Session_t *pSession) { size_t i = 0; while ((++pSession->counter[i] == 0) && (++i < AES_BLOCK_SIZE)) ; } /** * Encrypts the given character and writes it to the output buffer. * * @param[in,out] pSession A reference to a converter session. * @param[in] ch The character to encrypt and write. * @param[in,out] pOutput The output from the conversion process. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_WriteEncryptedChar(FwdLockConv_Session_t *pSession, unsigned char ch, FwdLockConv_Output_t *pOutput) { if (pOutput->fromConvertData.numBytes == pSession->outputBufferSize) { void *pBuffer; pSession->outputBufferSize += OUTPUT_BUFFER_SIZE_INCREMENT; pBuffer = realloc(pOutput->fromConvertData.pBuffer, pSession->outputBufferSize); if (pBuffer == NULL) { return FwdLockConv_Status_OutOfMemory; } pOutput->fromConvertData.pBuffer = pBuffer; } if (++pSession->keyStreamIndex == AES_BLOCK_SIZE) { FwdLockConv_IncrementCounter(pSession); pSession->keyStreamIndex = 0; } if (pSession->keyStreamIndex == 0) { AES_encrypt(pSession->counter, pSession->keyStream, &pSession->encryptionRoundKeys); } ch ^= pSession->keyStream[pSession->keyStreamIndex]; ((unsigned char *)pOutput->fromConvertData.pBuffer)[pOutput->fromConvertData.numBytes++] = ch; ++pSession->numDataBytes; return FwdLockConv_Status_OK; } /** * Matches binary-encoded content data and encrypts it, while looking out for the close delimiter. * * @param[in,out] pSession A reference to a converter session. * @param[in] ch A character. * @param[in,out] pOutput The output from the conversion process. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_MatchBinaryEncodedData(FwdLockConv_Session_t *pSession, int ch, FwdLockConv_Output_t *pOutput) { FwdLockConv_Status_t status = FwdLockConv_Status_OK; switch (pSession->scannerState) { case FwdLockConv_ScannerState_WantsByte1: if (ch != pSession->delimiter[pSession->delimiterMatchPos]) { // The partial match of the delimiter turned out to be spurious. Flush the matched bytes // to the output buffer and start over. size_t i; for (i = 0; i < pSession->delimiterMatchPos; ++i) { status = FwdLockConv_WriteEncryptedChar(pSession, pSession->delimiter[i], pOutput); if (status != FwdLockConv_Status_OK) { return status; } } pSession->delimiterMatchPos = 0; } if (ch != pSession->delimiter[pSession->delimiterMatchPos]) { // The current character isn't part of the delimiter. Write it to the output buffer. status = FwdLockConv_WriteEncryptedChar(pSession, ch, pOutput); } else if (++pSession->delimiterMatchPos == pSession->delimiterLength) { // The entire delimiter has been matched. The only valid characters now are the "--" // that complete the close delimiter (no more message parts are expected). pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash; } break; case FwdLockConv_ScannerState_WantsFirstDash: if (ch == '-') { pSession->scannerState = FwdLockConv_ScannerState_WantsSecondDash; } else { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsSecondDash: if (ch == '-') { pSession->parserState = FwdLockConv_ParserState_Done; } else { status = FwdLockConv_Status_SyntaxError; } break; default: status = FwdLockConv_Status_ProgramError; break; } return status; } /** * Checks whether a given character is valid in base64-encoded data. * * @param[in] ch The character to check. * * @return A Boolean value indicating whether the given character is valid in base64-encoded data. */ static int FwdLockConv_IsBase64Char(int ch) { return 0 <= ch && ch <= 'z' && base64Values[ch] >= 0; } /** * Matches base64-encoded content data and encrypts it, while looking out for the close delimiter. * * @param[in,out] pSession A reference to a converter session. * @param[in] ch A character. * @param[in,out] pOutput The output from the conversion process. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_MatchBase64EncodedData(FwdLockConv_Session_t *pSession, int ch, FwdLockConv_Output_t *pOutput) { FwdLockConv_Status_t status = FwdLockConv_Status_OK; switch (pSession->scannerState) { case FwdLockConv_ScannerState_WantsByte1: case FwdLockConv_ScannerState_WantsByte1_AfterCRLF: if (FwdLockConv_IsBase64Char(ch)) { pSession->ch = base64Values[ch] << 2; pSession->scannerState = FwdLockConv_ScannerState_WantsByte2; } else if (ch == '\r') { pSession->savedScannerState = FwdLockConv_ScannerState_WantsByte1_AfterCRLF; pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else if (ch == '-') { if (pSession->scannerState == FwdLockConv_ScannerState_WantsByte1_AfterCRLF) { pSession->delimiterMatchPos = 3; pSession->scannerState = FwdLockConv_ScannerState_WantsDelimiter; } else { status = FwdLockConv_Status_SyntaxError; } } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsByte2: if (FwdLockConv_IsBase64Char(ch)) { pSession->ch |= base64Values[ch] >> 4; status = FwdLockConv_WriteEncryptedChar(pSession, pSession->ch, pOutput); if (status == FwdLockConv_Status_OK) { pSession->ch = base64Values[ch] << 4; pSession->scannerState = FwdLockConv_ScannerState_WantsByte3; } } else if (ch == '\r') { pSession->savedScannerState = pSession->scannerState; pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsByte3: if (FwdLockConv_IsBase64Char(ch)) { pSession->ch |= base64Values[ch] >> 2; status = FwdLockConv_WriteEncryptedChar(pSession, pSession->ch, pOutput); if (status == FwdLockConv_Status_OK) { pSession->ch = base64Values[ch] << 6; pSession->scannerState = FwdLockConv_ScannerState_WantsByte4; } } else if (ch == '\r') { pSession->savedScannerState = pSession->scannerState; pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else if (ch == '=') { pSession->scannerState = FwdLockConv_ScannerState_WantsPadding; } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsByte4: if (FwdLockConv_IsBase64Char(ch)) { pSession->ch |= base64Values[ch]; status = FwdLockConv_WriteEncryptedChar(pSession, pSession->ch, pOutput); if (status == FwdLockConv_Status_OK) { pSession->scannerState = FwdLockConv_ScannerState_WantsByte1; } } else if (ch == '\r') { pSession->savedScannerState = pSession->scannerState; pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else if (ch == '=') { pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace; } else if (!FwdLockConv_IsWhitespace(ch)) { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsLF: if (ch == '\n') { pSession->scannerState = pSession->savedScannerState; } else { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsPadding: if (ch == '=') { pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace; } else { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsWhitespace: case FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF: if (ch == '\r') { pSession->savedScannerState = FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF; pSession->scannerState = FwdLockConv_ScannerState_WantsLF; } else if (ch == '-') { if (pSession->scannerState == FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF) { pSession->delimiterMatchPos = 3; pSession->scannerState = FwdLockConv_ScannerState_WantsDelimiter; } else { status = FwdLockConv_Status_SyntaxError; } } else if (FwdLockConv_IsWhitespace(ch)) { pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace; } else { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsDelimiter: if (ch != pSession->delimiter[pSession->delimiterMatchPos]) { status = FwdLockConv_Status_SyntaxError; } else if (++pSession->delimiterMatchPos == pSession->delimiterLength) { pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash; } break; case FwdLockConv_ScannerState_WantsFirstDash: if (ch == '-') { pSession->scannerState = FwdLockConv_ScannerState_WantsSecondDash; } else { status = FwdLockConv_Status_SyntaxError; } break; case FwdLockConv_ScannerState_WantsSecondDash: if (ch == '-') { pSession->parserState = FwdLockConv_ParserState_Done; } else { status = FwdLockConv_Status_SyntaxError; } break; default: status = FwdLockConv_Status_ProgramError; break; } return status; } /** * Pushes a single character into the converter's state machine. * * @param[in,out] pSession A reference to a converter session. * @param[in] ch A character. * @param[in,out] pOutput The output from the conversion process. * * @return A status code. */ static FwdLockConv_Status_t FwdLockConv_PushChar(FwdLockConv_Session_t *pSession, int ch, FwdLockConv_Output_t *pOutput) { FwdLockConv_Status_t status; ++pSession->numCharsConsumed; switch (pSession->parserState) { case FwdLockConv_ParserState_WantsOpenDelimiter: status = FwdLockConv_MatchOpenDelimiter(pSession, ch); break; case FwdLockConv_ParserState_WantsMimeHeaders: status = FwdLockConv_MatchMimeHeaders(pSession, ch, pOutput); break; case FwdLockConv_ParserState_WantsBinaryEncodedData: status = FwdLockConv_MatchBinaryEncodedData(pSession, ch, pOutput); break; case FwdLockConv_ParserState_WantsBase64EncodedData: if (ch == '\n' && pSession->scannerState != FwdLockConv_ScannerState_WantsLF) { // Repair base64-encoded data that doesn't have carriage returns in its line breaks. status = FwdLockConv_MatchBase64EncodedData(pSession, '\r', pOutput); if (status != FwdLockConv_Status_OK) { break; } } status = FwdLockConv_MatchBase64EncodedData(pSession, ch, pOutput); break; case FwdLockConv_ParserState_Done: status = FwdLockConv_Status_OK; break; default: status = FwdLockConv_Status_ProgramError; break; } return status; } FwdLockConv_Status_t FwdLockConv_OpenSession(int *pSessionId, FwdLockConv_Output_t *pOutput) { FwdLockConv_Status_t status; if (pSessionId == NULL || pOutput == NULL) { status = FwdLockConv_Status_InvalidArgument; } else { *pSessionId = FwdLockConv_AcquireSession(); if (*pSessionId < 0) { status = FwdLockConv_Status_TooManySessions; } else { FwdLockConv_Session_t *pSession = sessionPtrs[*pSessionId]; pSession->encryptedSessionKeyLength = FwdLockGlue_GetEncryptedKeyLength(KEY_SIZE); if (pSession->encryptedSessionKeyLength < AES_BLOCK_SIZE) { // The encrypted session key is used as the CTR-mode nonce, so it must be at least // the size of a single AES block. status = FwdLockConv_Status_ProgramError; } else { pSession->pEncryptedSessionKey = malloc(pSession->encryptedSessionKeyLength); if (pSession->pEncryptedSessionKey == NULL) { status = FwdLockConv_Status_OutOfMemory; } else { if (!FwdLockGlue_GetRandomNumber(pSession->sessionKey, KEY_SIZE)) { status = FwdLockConv_Status_RandomNumberGenerationFailed; } else if (!FwdLockGlue_EncryptKey(pSession->sessionKey, KEY_SIZE, pSession->pEncryptedSessionKey, pSession->encryptedSessionKeyLength)) { status = FwdLockConv_Status_KeyEncryptionFailed; } else { status = FwdLockConv_DeriveKeys(pSession); } if (status == FwdLockConv_Status_OK) { memset(pSession->sessionKey, 0, KEY_SIZE); // Zero out key data. memcpy(pSession->counter, pSession->pEncryptedSessionKey, AES_BLOCK_SIZE); pSession->parserState = FwdLockConv_ParserState_WantsOpenDelimiter; pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash; pSession->numCharsConsumed = 0; pSession->delimiterMatchPos = 0; pSession->mimeHeaderName = nullString; pSession->contentType = nullString; pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Undefined; pSession->keyStreamIndex = -1; pOutput->fromConvertData.pBuffer = NULL; pOutput->fromConvertData.errorPos = INVALID_OFFSET; } else { free(pSession->pEncryptedSessionKey); } } } if (status != FwdLockConv_Status_OK) { FwdLockConv_ReleaseSession(*pSessionId); *pSessionId = -1; } } } return status; } FwdLockConv_Status_t FwdLockConv_ConvertData(int sessionId, const void *pBuffer, size_t numBytes, FwdLockConv_Output_t *pOutput) { FwdLockConv_Status_t status; if (!FwdLockConv_IsValidSession(sessionId) || pBuffer == NULL || pOutput == NULL) { status = FwdLockConv_Status_InvalidArgument; } else { size_t i; FwdLockConv_Session_t *pSession = sessionPtrs[sessionId]; pSession->dataOffset = 0; pSession->numDataBytes = 0; pOutput->fromConvertData.numBytes = 0; status = FwdLockConv_Status_OK; for (i = 0; i < numBytes; ++i) { status = FwdLockConv_PushChar(pSession, ((char *)pBuffer)[i], pOutput); if (status != FwdLockConv_Status_OK) { break; } } if (status == FwdLockConv_Status_OK) { // Update the data signature. HMAC_Update(&pSession->signingContext, &((unsigned char *)pOutput->fromConvertData.pBuffer)[pSession->dataOffset], pSession->numDataBytes); } else if (status == FwdLockConv_Status_SyntaxError) { pOutput->fromConvertData.errorPos = pSession->numCharsConsumed; } } return status; } FwdLockConv_Status_t FwdLockConv_CloseSession(int sessionId, FwdLockConv_Output_t *pOutput) { FwdLockConv_Status_t status; if (!FwdLockConv_IsValidSession(sessionId) || pOutput == NULL) { status = FwdLockConv_Status_InvalidArgument; } else { FwdLockConv_Session_t *pSession = sessionPtrs[sessionId]; free(pOutput->fromConvertData.pBuffer); if (pSession->parserState != FwdLockConv_ParserState_Done) { pOutput->fromCloseSession.errorPos = pSession->numCharsConsumed; status = FwdLockConv_Status_SyntaxError; } else { // Finalize the data signature. unsigned int signatureSize = SHA1_HASH_SIZE; HMAC_Final(&pSession->signingContext, pOutput->fromCloseSession.signatures, &signatureSize); if (signatureSize != SHA1_HASH_SIZE) { status = FwdLockConv_Status_ProgramError; } else { // Calculate the header signature, which is a signature of the rest of the header // including the data signature. HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL); HMAC_Update(&pSession->signingContext, pSession->topHeader, TOP_HEADER_SIZE); HMAC_Update(&pSession->signingContext, (unsigned char *)pSession->contentType.ptr, pSession->contentType.length); HMAC_Update(&pSession->signingContext, pSession->pEncryptedSessionKey, pSession->encryptedSessionKeyLength); HMAC_Update(&pSession->signingContext, pOutput->fromCloseSession.signatures, SHA1_HASH_SIZE); HMAC_Final(&pSession->signingContext, &pOutput->fromCloseSession.signatures[SHA1_HASH_SIZE], &signatureSize); if (signatureSize != SHA1_HASH_SIZE) { status = FwdLockConv_Status_ProgramError; } else { pOutput->fromCloseSession.fileOffset = TOP_HEADER_SIZE + pSession->contentType.length + pSession->encryptedSessionKeyLength; status = FwdLockConv_Status_OK; } } pOutput->fromCloseSession.errorPos = INVALID_OFFSET; } free(pSession->mimeHeaderName.ptr); free(pSession->contentType.ptr); free(pSession->pEncryptedSessionKey); HMAC_CTX_cleanup(&pSession->signingContext); FwdLockConv_ReleaseSession(sessionId); } return status; } FwdLockConv_Status_t FwdLockConv_ConvertOpenFile(int inputFileDesc, FwdLockConv_ReadFunc_t *fpReadFunc, int outputFileDesc, FwdLockConv_WriteFunc_t *fpWriteFunc, FwdLockConv_LSeekFunc_t *fpLSeekFunc, off64_t *pErrorPos) { FwdLockConv_Status_t status; if (pErrorPos != NULL) { *pErrorPos = INVALID_OFFSET; } if (fpReadFunc == NULL || fpWriteFunc == NULL || fpLSeekFunc == NULL || inputFileDesc < 0 || outputFileDesc < 0) { status = FwdLockConv_Status_InvalidArgument; } else { char *pReadBuffer = malloc(READ_BUFFER_SIZE); if (pReadBuffer == NULL) { status = FwdLockConv_Status_OutOfMemory; } else { int sessionId; FwdLockConv_Output_t output; status = FwdLockConv_OpenSession(&sessionId, &output); if (status == FwdLockConv_Status_OK) { ssize_t numBytesRead; FwdLockConv_Status_t closeStatus; while ((numBytesRead = fpReadFunc(inputFileDesc, pReadBuffer, READ_BUFFER_SIZE)) > 0) { status = FwdLockConv_ConvertData(sessionId, pReadBuffer, (size_t)numBytesRead, &output); if (status == FwdLockConv_Status_OK) { if (output.fromConvertData.pBuffer != NULL && output.fromConvertData.numBytes > 0) { ssize_t numBytesWritten = fpWriteFunc(outputFileDesc, output.fromConvertData.pBuffer, output.fromConvertData.numBytes); if (numBytesWritten != (ssize_t)output.fromConvertData.numBytes) { status = FwdLockConv_Status_FileWriteError; break; } } } else { if (status == FwdLockConv_Status_SyntaxError && pErrorPos != NULL) { *pErrorPos = output.fromConvertData.errorPos; } break; } } // end while if (numBytesRead < 0) { status = FwdLockConv_Status_FileReadError; } closeStatus = FwdLockConv_CloseSession(sessionId, &output); if (status == FwdLockConv_Status_OK) { if (closeStatus != FwdLockConv_Status_OK) { if (closeStatus == FwdLockConv_Status_SyntaxError && pErrorPos != NULL) { *pErrorPos = output.fromCloseSession.errorPos; } status = closeStatus; } else if (fpLSeekFunc(outputFileDesc, output.fromCloseSession.fileOffset, SEEK_SET) < 0) { status = FwdLockConv_Status_FileSeekError; } else if (fpWriteFunc(outputFileDesc, output.fromCloseSession.signatures, FWD_LOCK_SIGNATURES_SIZE) != FWD_LOCK_SIGNATURES_SIZE) { status = FwdLockConv_Status_FileWriteError; } } } free(pReadBuffer); } } return status; }