diff options
| author | Chris Engel <cjengel@us.ibm.com> | 2017-08-22 14:09:18 -0500 |
|---|---|---|
| committer | Chris Engel <cjengel@us.ibm.com> | 2017-08-28 16:17:16 -0500 |
| commit | 30409d59f60314da8e060ae4af18774289073752 (patch) | |
| tree | 7d57265817454e197eb488aaa029e2691f1ca414 /src/client | |
| parent | 200cf3ccb19e801707cc824f62d143e80dff1b09 (diff) | |
| download | sb-signing-framework-30409d59f60314da8e060ae4af18774289073752.tar.gz sb-signing-framework-30409d59f60314da8e060ae4af18774289073752.zip | |
Initial drop of signing framework client
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/client.c | 663 | ||||
| -rw-r--r-- | src/client/makefile | 40 | ||||
| -rw-r--r-- | src/client/pscp_json.c | 448 | ||||
| -rw-r--r-- | src/client/pscp_json.h | 26 | ||||
| -rw-r--r-- | src/client/pscp_sftp.c | 462 | ||||
| -rw-r--r-- | src/client/pscp_sftp.h | 32 | ||||
| -rw-r--r-- | src/client/pscp_sftp_test.c | 67 |
7 files changed, 1738 insertions, 0 deletions
diff --git a/src/client/client.c b/src/client/client.c new file mode 100644 index 0000000..06cd91b --- /dev/null +++ b/src/client/client.c @@ -0,0 +1,663 @@ +/* Copyright 2017 IBM Corp. + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <unistd.h> +#include <stdbool.h> +#include <fcntl.h> + +#include "pscp_sftp.h" +#include "pscp_json.h" + +bool GetArgs(int argc, char **argv); +void PrintUsage(void); +bool PrivateKeyEncrypted(const char* privKeyPath); +int GenerateFilename(char** filename, const char* directory, const char* base, const char* tag, bool verbose); + +const char* pscp_request_tag = ".request"; +const char* pscp_request_go_tag = ".request.go"; +const char* pscp_response_tag = ".response"; +const char* pscp_response_go_tag = ".response.go"; +const char* pscp_remote_directory = "dropbox/"; +const char* pscp_tmp_directory = "/tmp/"; + + +// Required Parameters +char* pscp_project = NULL; +char* pscp_epwd_path = NULL; +char* pscp_sftp_url = NULL; +char* pscp_pkey_path = NULL; +char* pscp_comment = NULL; +// Optional Parameters +char* pscp_parameters = NULL; +char* pscp_payload_path = NULL; +char* pscp_output_file = NULL; +bool verbose = false; +bool debug = false; +bool pscp_print_server_stdout = false; + +int main(int argc, char** argv) +{ + char* pscp_identifier = NULL; + char* json_string = NULL; + char* pscp_session_id = NULL; + char* pscp_request_filename = NULL; + char* pscp_request_go_filename = NULL; + char* pscp_response_filename = NULL; + char* pscp_response_go_filename = NULL; + + struct pscp_sftp_session* pscp_sftp_session = NULL; + + int status = 0; // Indicates what type of failure occured + bool success = GetArgs(argc, argv); // Used to check failure in code + + if(!success) + { + // This silences the generic "ERROR" message that occurs if status is not set to something specific by GetArgs() + status = -1; + } + + // Verify that required parameters are found + if(success && !(pscp_project && pscp_epwd_path && pscp_sftp_url && pscp_pkey_path)) + { + success = false; + fprintf(stderr, "ERROR: not all required parameters were found\n"); + PrintUsage(); + } + + // Check that the private key is encrypted + if(success) + { + if(!PrivateKeyEncrypted(pscp_pkey_path)) + { + success = false; + fprintf(stderr, "ERROR: RSA private key must be encrypted\n"); + } + } + + // Create the identifier for the user: <username>_<hostname> + if(success) + { + const char * username = getenv("USER"); + const char * hostname = getenv("HOSTNAME"); + const char * nullString = "NULL"; // Used if no username or hostname is found + + size_t identifierMaxSize = strlen(username) + + strlen(hostname) + + 2; // Seperator and \0 + + pscp_identifier = (char*)calloc(identifierMaxSize, 1); + + if(pscp_identifier) + { + if(username && hostname) + { + snprintf(pscp_identifier, identifierMaxSize, "%s_%s", username, hostname); + } + else if(username) + { + fprintf(stderr, "ERROR: Unable to find hostname in the environment\n"); + strncpy(pscp_identifier, username, identifierMaxSize); + } + else if(hostname) + { + fprintf(stderr, "ERROR: Unable to find username in the environment\n"); + strncpy(pscp_identifier, hostname, identifierMaxSize); + } + else + { + fprintf(stderr, "ERROR: Unable to find username or hostname in the environment\n"); + strncpy(pscp_identifier, nullString, identifierMaxSize); + } + + if(strlen(pscp_identifier) == 0) + { + fprintf(stderr, "ERROR: Failure in generation of pscp_identifier\n"); + success = false; + } + } + else + { + fprintf(stderr, "ERROR: Unable to allocate memory for pscp_identifier\n"); + success = false; + } + } + + // Create the session id: <username>_<hostname>_<timestamp>. This is also used as the base for filenames. + if(success) + { + time_t timestamp = time(NULL); + size_t len = strlen(pscp_identifier) + + (((int)log10(timestamp))+1) + + 2; + pscp_session_id = (char*)calloc(len, 1); + if(pscp_session_id) + { + int rc = snprintf(pscp_session_id, len, "%s_%lx", pscp_identifier, timestamp); + if(rc <= 0) + { + success = false; + fprintf(stderr, "ERROR: snprintf for generating pscp_session_id failed\n"); + } + } + else + { + success = false; + fprintf(stderr, "ERROR: unable to allocate memory for pscp_session_id\n"); + } + } + + // Generate the filenames for communication with the server + if(success) + { + status = GenerateFilename(&pscp_request_filename, pscp_tmp_directory, pscp_session_id, pscp_request_tag, verbose); + if(status != 0) + { + fprintf(stderr, "ERROR: could not generate name for server-side %s file\n", pscp_request_tag); + success = false; + } + } + if(success) + { + status = GenerateFilename(&pscp_request_go_filename, pscp_tmp_directory, pscp_session_id, pscp_request_go_tag, verbose); + if(status != 0) + { + fprintf(stderr, "ERROR: could not generate name for server-side %s file\n", pscp_request_go_tag); + success = false; + } + } + if(success) + { + status = GenerateFilename(&pscp_response_filename, pscp_tmp_directory, pscp_session_id, pscp_response_tag, verbose); + if(status != 0) + { + success = false; + fprintf(stderr, "ERROR: could not generate name for server-side %s file\n", pscp_response_tag); + } + } + if(success) + { + status = GenerateFilename(&pscp_response_go_filename, pscp_tmp_directory, pscp_session_id, pscp_response_go_tag, verbose); + if(status != 0) + { + success = false; + fprintf(stderr, "ERROR: could not generate name for server-side %s file\n", pscp_response_go_tag); + } + } + + // Generate the json from the parameters + if(success) + { + int rc = createJsonString(&json_string, pscp_project, pscp_parameters, pscp_identifier, pscp_comment, pscp_epwd_path, pscp_payload_path, verbose); + if(rc != 0) + { + fprintf(stderr, "ERROR: unable to create json: %d\n", rc); + success = false; + status = rc; + } + } + + // Write json string to a new request file + if(success) + { + // Restrict access to request file to protect the encrypted password + int fd = open(pscp_request_filename, O_CREAT | O_WRONLY, 0600); + if(fd >= 0) + { + FILE* fp = fdopen(fd, "w"); + if(fp) + { + size_t json_string_len = strlen(json_string); + size_t len_write = fwrite(json_string, 1, json_string_len, fp); + if(len_write != json_string_len) + { + fprintf(stderr, "ERROR: write to file was unsuccesful\n"); + success = false; + } + fclose(fp); + } + else + { + success = false; + fprintf(stderr, "ERROR: unable to convert file descriptor to FILE type: %s\n", pscp_request_filename); + } + close(fd); + } + else + { + success = false; + fprintf(stderr, "ERROR: unable to open request file for writing: %s\n", pscp_request_filename); + } + } + + // Initialize the sftp library + if(success) + { + int rc = pscp_sftp_global_init(); + if(rc != 0) + { + fprintf(stderr, "ERROR in sftp library initialization\n"); + success = false; + status = rc; + } + } + + // Create the sftp session + if(success) + { + pscp_sftp_session = startSftpSession(pscp_sftp_url, pscp_pkey_path, verbose); + if(!pscp_sftp_session) + { + fprintf(stderr, "ERROR: unable to create pscp_curl session\n"); + success = false; + } + } + + // Send request file server + if(success) + { + char* remote_request_filename = NULL; + status = GenerateFilename(&remote_request_filename, pscp_remote_directory, pscp_session_id, pscp_request_tag, verbose); + if(status == 0) + { + status = sendFileToServer(pscp_sftp_session, pscp_request_filename, remote_request_filename); + free(remote_request_filename); + if(status != 0) + { + success = false; + fprintf(stderr, "ERROR: curl send failed for file %s\n", pscp_request_filename); + } + } + else + { + success = false; + fprintf(stderr, "ERROR: couldn't allocate memory for remote file name\n"); + } + } + + // Send the GO file to server to start the signing + if(success) + { + FILE * fp = fopen(pscp_request_go_filename, "w"); + if(fp) + { + fclose(fp); + char* remote_request_go_filename = NULL; + status = GenerateFilename(&remote_request_go_filename, pscp_remote_directory, pscp_session_id, pscp_request_go_tag, verbose); + if(status == 0) + { + status = sendFileToServer(pscp_sftp_session, pscp_request_go_filename, remote_request_go_filename); + if(status != 0) + { + success = false; + fprintf(stderr, "ERROR: curl send failed for file %s\n", pscp_request_go_filename); + } + free(remote_request_go_filename); + } + else + { + success = false; + fprintf(stderr, "ERROR: couldn't allocate memory for remote file name\n"); + } + } + else + { + success = false; + fprintf(stderr, "ERROR: unable to open request.go file for writing: %s\n", pscp_request_go_filename); + } + } + + // Wait for signing server to create the response GO file + if(success) + { + char* remote_response_go_filename = NULL; + status = GenerateFilename(&remote_response_go_filename, pscp_remote_directory, pscp_session_id, pscp_response_go_tag, verbose); + if(status == 0) + { + status = pollOnFileFromServer(pscp_sftp_session, pscp_response_go_filename, remote_response_go_filename); + if(status != 0) + { + success = false; + fprintf(stderr, "ERROR: no response from server\n"); + } + free(remote_response_go_filename); + } + else + { + success = false; + fprintf(stderr, "ERROR: could not generate %s filename\n", pscp_response_go_tag); + } + } + + // Copy the signing server response file to the client machine + if(success) + { + char* remote_response_filename = NULL; + status = GenerateFilename(&remote_response_filename, pscp_remote_directory, pscp_session_id, pscp_response_tag, verbose); + if(status == 0) + { + status = getFileFromServer(pscp_sftp_session, pscp_response_filename, remote_response_filename); + if(status != 0) + { + success = false; + fprintf(stderr, "ERROR: unable to retreive file from server\n"); + } + free(remote_response_filename); + } + else + { + success = false; + fprintf(stderr, "ERROR: unable to generate remote filename\n"); + } + } + + // Parse data from the server response and display it appropriately + if(success) + { + int retval = 0; + unsigned char* payload = NULL; + char* stdout = NULL; + size_t payload_len = 0; + size_t stdout_len = 0; + + status = parseServerResponse(pscp_response_filename, &payload, &payload_len, &stdout, &stdout_len, &retval, verbose); + if(status == 0) + { + status = retval; + + if(payload) + { + if(pscp_output_file) + { + FILE* fp = fopen(pscp_output_file, "w"); + if(fp) + { + size_t len = fwrite(payload, 1, payload_len, fp); + if(len != payload_len) + { + success = false; + fprintf(stderr, "ERROR: unable to write server payload to file. Only %lu/%lu bytes written\n", len, payload_len); + } + fclose(fp); + } + else + { + success = false; + fprintf(stderr, "ERROR: unable to open output file\n"); + } + } + else + { + success = false; + fprintf(stderr, "ERROR: no output filename was provided, unable to write reponse payload\n"); + } + free(payload); + payload = NULL; + } + if(stdout) + { + if(pscp_print_server_stdout) + { + printf("\n==== Begin Standard Out ====\n%s\n==== End of Standard Out ====\n", stdout); + } + free(stdout); + } + if(retval != 0) + { + fprintf(stderr, "Signing server responded with failure: %d\n" + "Rerun with -stdout to see server output\n", retval); + } + } + else + { + success = false; + fprintf(stderr, "ERROR: could not parse server response\n"); + } + } + + // Remove the created files from the local machine + if(!debug) + { + if(verbose) printf("Removing auto-generated files\n"); + remove(pscp_request_filename); + remove(pscp_request_go_filename); + remove(pscp_response_filename); + remove(pscp_response_go_filename); + } + + if(success && debug) + { + printf("\nServer response written to: %s\n", pscp_response_filename); + } + + if(pscp_sftp_session) + { + closeSftpSession(pscp_sftp_session); + pscp_sftp_session = NULL; + } + if(pscp_identifier) + { + free(pscp_identifier); + pscp_identifier = NULL; + } + if(json_string) + { + free(json_string); + json_string = NULL; + } + if(pscp_session_id) + { + free(pscp_session_id); + pscp_session_id = NULL; + } + if(pscp_request_filename) + { + free(pscp_request_filename); + pscp_request_filename = NULL; + } + if(pscp_request_go_filename) + { + free(pscp_request_go_filename); + pscp_request_go_filename = NULL; + } + if(pscp_response_filename) + { + free(pscp_response_filename); + pscp_response_filename = NULL; + } + if(pscp_response_go_filename) + { + free(pscp_response_go_filename); + pscp_response_go_filename = NULL; + } + + if(!success && (status == 0)) + { + // Set generic error status for non-specific error + status = -1; + fprintf(stderr, "ERROR\n"); + } + + if(success && (status == 0)) + { + printf("DONE\n"); + } + + return status; +} + +bool GetArgs(int argc, char **argv) +{ + bool success = true; + int i; + + /* command line argument defaults */ + verbose = false; + + /* get the command line arguments */ + for (i=1 ; (i<argc) && (success) ; i++) { + if (strcmp(argv[i],"-v") == 0) { + verbose = true; + } + else if (strcmp(argv[i],"-d") == 0) { + debug = true; + } + else if (strcmp(argv[i],"-project") == 0) { + i++; + pscp_project = argv[i]; + } + else if (strcmp(argv[i],"-param") == 0) { + i++; + pscp_parameters = argv[i]; + } + else if (strcmp(argv[i],"-epwd") == 0) { + i++; + pscp_epwd_path = argv[i]; + } + else if (strcmp(argv[i],"-payload") == 0) { + i++; + pscp_payload_path = argv[i]; + } + else if (strcmp(argv[i],"-comments") == 0) { + i++; + pscp_comment = argv[i]; + } + else if (strcmp(argv[i],"-url") == 0) { + i++; + pscp_sftp_url = argv[i]; + } + else if (strcmp(argv[i],"-pkey") == 0) { + i++; + pscp_pkey_path = argv[i]; + } + else if (strcmp(argv[i],"-stdout") == 0) { + pscp_print_server_stdout = true; + } + else if (strcmp(argv[i],"-o") == 0) { + i++; + pscp_output_file = argv[i]; + } + else if (strcmp(argv[i],"-h") == 0) { + PrintUsage(); + success = false; + } + else { + fprintf(stderr, "\nframework: Error, %s is not a valid option\n",argv[i]); + PrintUsage(); + success = false; + } + } + return success; +} + +void PrintUsage() +{ + printf("\n"); + printf("client:\n" + "\t-h \t print usage help\n\n" + "\tRequired:\n" + "\t\t-project '' - Name of the project\n" + "\t\t-comments '' - Identifier/Message for audit log\n" + "\t\t-epwd 'path' - File path to the hsm encrypted password\n" + "\t\t-url '' - sftp url. Example: sftp://user@address\n" + "\t\t-pkey 'path' - File path to the *encrypted* private key file\n" + "\tOptional:\n" + "\t\t-payload <path> - File path to the binary to be signed\n" + "\t\t-param '' - Parameters to be passed to the signing framework. Ex '-v' or '-h'\n" + "\t\t-o <file> - output file to save the return payload\n" + "\t\t-stdout - Displays the stdout from the server\n" + "\tDebugging:\n" + "\t\t-v - verbose tracing\n" + "\t\t-d - debug mode - files will not be deleted\n"); + printf("\n"); + return; +} + +// Verifies that the provided private key is encrypted. +bool PrivateKeyEncrypted(const char* privKeyPath) +{ + bool encrypted = false; + FILE * key = fopen(privKeyPath, "r"); + fseek(key, 0, SEEK_END); + int len = ftell(key); + rewind(key); + + char * privKey = calloc(len+1, 1); + if(privKey) + { + size_t read_len = fread(privKey, 1, len, key); + + if(read_len == len) + { + char* location = strstr(privKey, "ENCRYPTED"); + if(location) + { + encrypted = true; + } + } + free(privKey); + privKey = NULL; + } + return encrypted; +} + +// Merges the directory, base, and tag into a single string. +int GenerateFilename(char** filename, const char* directory, const char* base, const char* tag, bool verbose) +{ + int status = 0; + if(filename && directory && base && tag) + { + if(*filename) + { + free(*filename); + *filename = NULL; + } + size_t len = strlen(directory) + strlen(base) + strlen(tag) + 1; + *filename = (char*)calloc(len, 1); + + if(*filename) + { + int rc = snprintf(*filename, len, "%s%s%s", directory, base, tag); + if(rc <= 0) + { + free(*filename); + *filename = NULL; + fprintf(stderr, "ERROR: generation of filename failed:\n\tdirectory: %s\n\tbase: %s\n\ttag: %s\n", directory, base, tag); + status = -1; + } + else + { + if(verbose) printf("Filename Generated: %s\n", *filename); + } + } + else + { + fprintf(stderr, "ERROR: unable to allocate memory for filename:\n\tdirectory: %s\n\tbase: %s\n\ttag: %s\n", directory, base, tag); + status = -1; + } + } + else + { + fprintf(stderr, "ERROR: null value passed in as argument to generateFilename()\n"); + status = -1; + } + return status; +} + + diff --git a/src/client/makefile b/src/client/makefile new file mode 100644 index 0000000..d4dc827 --- /dev/null +++ b/src/client/makefile @@ -0,0 +1,40 @@ +# Copyright 2017 IBM Corp. +# +# 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. + + +CC=gcc +CFLAGS+=-std=gnu99 -Wall -pedantic -I. +CLIB=-ljson-c -lm -lcurl -lssl -lcrypto +DEPS = pscp_sftp.h pscp_json.h +CLIENT_OBJ = client.o +SFTP_T_OBJ = pscp_sftp_test.o +COMMON_OBJ = pscp_sftp.o pscp_json.o +CEXEC=sf_client sftp-test + +%.o: %.c $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +sf_client: $(CLIENT_OBJ) $(COMMON_OBJ) + gcc -o $@ $^ $(CFLAGS) $(CLIB) + +sftp-test: $(SFTP_T_OBJ) $(COMMON_OBJ) + gcc -o $@ $^ $(CFLAGS) $(CLIB) + +default: sf_client + +all: $(CEXEC) + +clean: + rm -f $(CEXEC) $(SFTP_T_OBJ) $(CLIENT_OBJ) $(DECRYPT_OBJ) $(COMMON_OBJ) diff --git a/src/client/pscp_json.c b/src/client/pscp_json.c new file mode 100644 index 0000000..f36d32f --- /dev/null +++ b/src/client/pscp_json.c @@ -0,0 +1,448 @@ +/* Copyright 2017 IBM Corp. + * + * 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 <string.h> +#include <stdio.h> +#include <ctype.h> + +#include <json-c/json.h> + +#include "pscp_json.h" + +char NibbleToChar(const unsigned char nibble, const bool verbose); +char * ByteArrayToHexAscii(const unsigned char * byte, const size_t len, const bool verbose); +int HexAsciiToByteArray(unsigned char** byte, size_t* byte_len, const char* ascii, const size_t ascii_len, bool verbose); +int BinaryFileToJsonObject(struct json_object** json, FILE* fp, const bool verbose); +void TrimWhitespace(char *str); + +char NibbleToChar(const unsigned char nibble, const bool verbose) +{ + if(nibble > 0xF) + { + if(verbose) fprintf(stderr, "ERROR: Data passed into nibbleToChar() is larger than a nibble\n"); + return 0; + } + else + { + if(nibble < 10) + { + return ('0' + nibble); + } + else + { + return ('A' + nibble - 10); + } + } +} + +unsigned char CharToNibble(char c, bool verbose) +{ + c = toupper(c); + if(c >= '0' && c <= '9') + { + return (c - '0'); + } + else if(c >= 'A' && c <= 'F') + { + return (c - 'A' + 10); + } + else + { + return 0xFF; + } +} + +char * ByteArrayToHexAscii(const unsigned char * byte, const size_t len, const bool verbose) +{ + size_t out_len = len * 2; + char* out_str = malloc(out_len + 1); + bool success = true; + + for(size_t i = 0; (i < len) && success; i++) + { + unsigned char hi = (byte[i] >> 4) & 0xF; + unsigned char lo = byte[i] & 0x0F; + char char_hi = NibbleToChar(hi, verbose); + char char_lo = NibbleToChar(lo, verbose); + if((char_hi != 0) && (char_lo != 0)) + { + out_str[2*i] = char_hi; + out_str[(2*i)+1] = char_lo; + } + else + { + if(verbose) fprintf(stderr, "Error in parsing binary file at index %x\n", (unsigned int)i); + success = false; + } + } + out_str[out_len] = 0; + + if(!success) + { + free(out_str); + out_str = NULL; + } + return out_str; +} + +int HexAsciiToByteArray(unsigned char** byte, size_t* byte_len, const char* ascii, const size_t ascii_len, bool verbose) +{ + bool success = true; + int status = 0; + if(byte && *byte == NULL && byte_len && ascii) + { + if(ascii_len % 2 == 0) + { + *byte_len = ascii_len / 2; + *byte = calloc(*byte_len, 1); + + for(size_t i = 0; (i < *byte_len) && success; i++) + { + unsigned char hi = CharToNibble(ascii[i*2], verbose); + unsigned char lo = CharToNibble(ascii[(i*2)+1], verbose); + if(hi != 0xFF && lo != 0xFF) + { + (*byte)[i] = (0xF0 & (hi << 4)) | (0x0F & lo); + } + else + { + if(verbose) fprintf(stderr, "Error in parsing binary from ascii at byte %lu\n", i); + success = false; + } + } + } + else + { + success = false; + } + if(!success && *byte) + { + free(*byte); + } + } + else + { + success = false; + } + if(status == 0 && !success) + { + status = -1; + } + + return status; +} + +int BinaryFileToJsonObject(struct json_object** json, FILE* fp, const bool verbose) +{ + int status = 0; + bool success = true; + + if(!(json && (*json == NULL) && fp)) + { + success = false; + } + + if(success) + { + fseek(fp, 0, SEEK_END); + int fileSize = ftell(fp); + rewind(fp); + + unsigned char* hashFile = malloc((fileSize+1)); + if(hashFile) + { + size_t read_len = fread(hashFile, 1, fileSize, fp); + if(read_len == fileSize) + { + char* hashString = ByteArrayToHexAscii(hashFile, read_len, verbose); + + if(hashString) + { + json_object* tmp = json_object_new_string(hashString); + if(tmp) + { + *json = tmp; + } + else + { + success = false; + } + free(hashString); + } + else + { + success = false; + } + } + else + { + success = false; + if(verbose) fprintf(stderr, "ERROR: difference between byte read and file size. File size: %d, Bytes read: %d\n", (int)fileSize, (int)read_len); + } + free(hashFile); + } + else + { + success = false; + if(verbose) fprintf(stderr, "ERROR: Unable to allocate memory in binaryFileToJsonObject\n"); + } + } + + if(!success && status == 0) + { + status = -1; + } + + return status; +} + +int createJsonString(char** rtnStr, const char* pscp_project, const char* pscp_parameters, const char* pscp_user, const char* pscp_comment, const char* pscp_epwd_path, const char* pscp_payload_path, const bool verbose) +{ + bool success = true; + int status = 0; + struct json_object* json = NULL; + char* nullStr = ""; + + // Check for required arguments + if(!(pscp_project && pscp_epwd_path && pscp_user && pscp_comment && rtnStr && (*rtnStr == NULL))) + { + success = false; + } + + if(success) + { + json = json_object_new_object(); + if(!json) + { + success = false; + } + } + + if(success) + { + if(!pscp_parameters) + { + pscp_parameters = nullStr; + } + } + + // Copy the strings into json objects + if(success) + { + struct json_object* json_project = json_object_new_string(pscp_project); + struct json_object* json_parameters = json_object_new_string(pscp_parameters); + struct json_object* json_user = json_object_new_string(pscp_user); + struct json_object* json_comment = json_object_new_string(pscp_comment); + + if(json_project && json_parameters && json_user && json_comment) + { + // Ownership of memory is passed to parent json_object + json_object_object_add(json, "project", json_project); + json_object_object_add(json, "parameters", json_parameters); + json_object_object_add(json, "user", json_user); + json_object_object_add(json, "comment", json_comment); + } + else + { + success = false; + if(json_project) json_object_put(json_project); + if(json_parameters) json_object_put(json_parameters); + if(json_user) json_object_put(json_user); + if(json_comment) json_object_put(json_comment); + } + } + + // Copy epwd file into json + if(success) + { + FILE * epwdFile = fopen(pscp_epwd_path, "r"); + if(epwdFile) + { + fseek(epwdFile, 0, SEEK_END); + int fileSize = ftell(epwdFile); + rewind(epwdFile); + + char* epwd = calloc(fileSize+1, 1); + if(epwd) + { + int len = fread(epwd, 1, fileSize, epwdFile); + if(len == fileSize) + { + TrimWhitespace(epwd); + struct json_object* json_epwd = json_object_new_string(epwd); + if(json_epwd) + { + json_object_object_add(json, "epwd", json_epwd); + } + else + { + success = false; + } + } + else + { + if(verbose) fprintf(stderr, "ERROR: Unable to read epwd file %s\n", pscp_epwd_path); + success = false; + } + free(epwd); + } + else + { + if(verbose) fprintf(stderr, "ERROR: unable to allocate memory for epwd\n"); + success = false; + } + } + else + { + success = false; + if(verbose) fprintf(stderr, "ERROR: Unable to open epwd file %s\n", pscp_epwd_path); + } + } + // Read in payload file (optional) + if(success && pscp_payload_path) + { + FILE* fp = fopen(pscp_payload_path, "r"); + if(fp) + { + struct json_object* json_payload = NULL; + int rc = BinaryFileToJsonObject(&json_payload, fp, verbose); + if(rc == 0) + { + json_object_object_add(json, "payload", json_payload); + } + else + { + if(verbose) fprintf(stderr, "ERROR: Unable to stringify binary file %s\n", pscp_payload_path); + success = false; + status = rc; + } + fclose(fp); + } + else + { + if(verbose) fprintf(stderr, "ERROR: Unable to open payload file %s\n", pscp_payload_path); + success = false; + } + } + + if(success) + { + const char* json_string = json_object_to_json_string(json);; + if(json_string) + { + // When the json object is free'd, the memory where the string comes from is also lost + *rtnStr = strdup(json_string); + } + else + { + if(verbose) fprintf(stderr, "ERROR: Unable to generate serialized json string\n"); + } + } + if(json) + { + json_object_put(json); + } + + if(!success && status == 0) + { + status = -1; + } + + return status; +} + +int parseServerResponse(const char * responseFile, unsigned char** res_payload, size_t* res_payload_len, char** res_stdout, size_t* res_stdout_len, int* res_retval, bool verbose) +{ + int retval = -1; + if(responseFile && res_payload && stdout && res_retval && res_payload_len && res_stdout_len) + { + FILE * fp = fopen(responseFile, "r"); + if(fp) + { + fseek(fp, 0, SEEK_END); + int fileSize = ftell(fp); + rewind(fp); + + *res_payload = NULL; + *res_stdout = NULL; + + char * response = malloc(fileSize+1); + if(response) + { + size_t len = fread(response, 1, fileSize, fp); + if(len == fileSize) + { + struct json_object* json = json_tokener_parse(response); + if(json) + { + json_object* tmp = NULL; + json_object_object_get_ex(json, "result", &tmp); + if(tmp) + { + const char* json_string = json_object_get_string(tmp); + size_t json_string_len = strlen(json_string); + HexAsciiToByteArray(res_payload, res_payload_len, json_string, json_string_len, verbose); + } + + tmp = NULL; + json_object_object_get_ex(json, "stdout", &tmp); + if(tmp) + { + *res_stdout = strdup(json_object_get_string(tmp)); + *res_stdout_len = strlen(*res_stdout); + } + + tmp = NULL; + json_object_object_get_ex(json, "retval", &tmp); + if(tmp) *res_retval = json_object_get_int(tmp); + retval = 0; + + json_object_put(json); + } + } + free(response); + } + fclose(fp); + } + } + return retval; +} + +// Removes any whitespace in the string +void TrimWhitespace(char *str) +{ + char* p = NULL; + char* q = NULL; + if(str) + { + p = str; + q = str; + while(*q != 0) + { + if(isspace(*q)) + { + q++; + } + else + { + *p = *q; + p++; + q++; + } + } + *p = 0; + } +} diff --git a/src/client/pscp_json.h b/src/client/pscp_json.h new file mode 100644 index 0000000..27802e1 --- /dev/null +++ b/src/client/pscp_json.h @@ -0,0 +1,26 @@ +/* Copyright 2017 IBM Corp. + * + * 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. + */ + +#ifndef PSCP_JSON_H +#define PSCP_JSON_H + +#include <stdbool.h> + +int createJsonString(char** rtnStr, const char* pscp_project, const char* pscp_parameters, const char* pscp_user, const char* pscp_comment, const char* pscp_epwd_path, const char* pscp_payload_path, const bool verbose); +//int encryptJsonString(const char * in, char** out, const size_t in_len, size_t* out_len, bool verbose); +int parseServerResponse(const char * responseFile, unsigned char** res_payload, size_t* res_payload_len, char** res_stdout, size_t* res_stdout_len, int* res_retval, bool verbose); + +#endif diff --git a/src/client/pscp_sftp.c b/src/client/pscp_sftp.c new file mode 100644 index 0000000..813950f --- /dev/null +++ b/src/client/pscp_sftp.c @@ -0,0 +1,462 @@ +/* Copyright 2017 IBM Corp. + * + * 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 "pscp_sftp.h" + +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <unistd.h> +#include <signal.h> + +#include <termios.h> +#include <curl/curl.h> + +#define PSCP_PKEY_PASSPHRASE_MAX 256 +#define PSCP_SFTP_MAX_POLLING_ATTEMPTS 10 +#define PSCP_SFTP_POLLING_DURATION 5 + +struct pscp_sftp_session +{ + CURL* curl; + char* url; + size_t url_len; + bool verbose; +}; + +int GetPassword(char* result, size_t len, bool verbose); + +int pscp_sftp_global_init() +{ + return curl_global_init(CURL_GLOBAL_ALL); +} + +// Common code to initialize sftp connection to remote server. +struct pscp_sftp_session* startSftpSession(const char * sftp_url, const char * privateKeyPath, bool verbose) +{ + int status = 0; + struct pscp_sftp_session* sftp = NULL; + + if(!sftp_url || !privateKeyPath) + { + status = -1; + } + + if(status == 0) + { + sftp = calloc(1, sizeof(struct pscp_sftp_session)); + if(!sftp) + { + status = -1; + } + } + + if(status == 0) + { + sftp->curl = curl_easy_init(); + if(!sftp->curl) + { + status = -1; + } + } + + if(status == 0 && verbose) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_VERBOSE, 1L); + } + + if(status == 0) + { + size_t len = strlen(sftp_url); + sftp->url_len = len + 2; + sftp->url = calloc(sftp->url_len, 1); + if(sftp->url) + { + strncpy(sftp->url, sftp_url, len); + + if(sftp_url[len - 1] != '/') + { + strncat(sftp->url, "/", sftp->url_len); + } + } + } + + if(status == CURLE_OK) status = curl_easy_setopt(sftp->curl, CURLOPT_PROTOCOLS, CURLPROTO_SFTP); + if(status == CURLE_OK) status = curl_easy_setopt(sftp->curl, CURLOPT_SSH_PRIVATE_KEYFILE, privateKeyPath); + if(status == CURLE_OK) + { + char passphrase[PSCP_PKEY_PASSPHRASE_MAX]; + bzero(passphrase, PSCP_PKEY_PASSPHRASE_MAX); + status = GetPassword(passphrase, PSCP_PKEY_PASSPHRASE_MAX, verbose); + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_KEYPASSWD, passphrase); + } + bzero(passphrase, PSCP_PKEY_PASSPHRASE_MAX); + } + + if(status != 0 && sftp) + { + if(sftp->curl) + { + curl_easy_cleanup(sftp->curl); + } + if(sftp->url) + { + free(sftp->url); + } + free(sftp); + sftp = NULL; + } + return sftp; +} + + +int sendFileToServer(const struct pscp_sftp_session* sftp, const char * local, const char * remote) +{ + int status = 0; + char* full_url = NULL; + FILE* fp = NULL; + + if(!sftp || !local || !remote) + { + status = -1; + } + else if(!sftp->curl || !sftp->url) + { + status = -1; + } + + if(status == 0) + { + size_t len = strlen(remote) + strlen(sftp->url) + 1; + full_url = calloc(len, 1); + if(full_url) + { + snprintf(full_url, len, "%s%s", sftp->url, remote); + } + else + { + status = -1; + } + } + + if(status == 0) + { + fp = fopen(local, "r"); + if(!fp) + { + status = -1; + } + } + + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_UPLOAD, 1L); + } + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_READDATA, fp); + } + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_URL, full_url); + } + if(status == 0) + { + status = curl_easy_perform(sftp->curl); + if(status == CURLE_LOGIN_DENIED) + { + fprintf(stderr, "%s rejected the provided credentials\n", sftp->url); + } + } + + if(fp) + { + fclose(fp); + fp = NULL; + } + if(full_url) + { + free(full_url); + full_url = NULL; + } + + return status; +} + +int getFileFromServer(const struct pscp_sftp_session* sftp, const char * local, const char * remote) +{ + int status = 0; + char* full_url = NULL; + FILE* fp = NULL; + + if(!sftp || !local || !remote) + { + status = -1; + } + else if(!sftp->curl || !sftp->url) + { + status = -1; + } + + if(status == 0) + { + size_t len = strlen(remote) + strlen(sftp->url) + 1; + full_url = calloc(len, 1); + if(full_url) + { + snprintf(full_url, len, "%s%s", sftp->url, remote); + } + else + { + status = -1; + } + } + + if(status == 0) + { + fp = fopen(local, "w"); + if(!fp) + { + status = -1; + } + } + + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_UPLOAD, 0L); + } + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_WRITEDATA, fp); + } + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_URL, full_url); + } + if(status == 0) + { + status = curl_easy_perform(sftp->curl); + if(status == CURLE_LOGIN_DENIED) + { + fprintf(stderr, "%s rejected the provided credentials\n", sftp->url); + } + } + + if(fp) + { + fclose(fp); + fp = NULL; + } + if(full_url) + { + free(full_url); + full_url = NULL; + } + + return status; +} + + +int pollOnFileFromServer(const struct pscp_sftp_session* sftp, const char * local, const char * remote) +{ + int status = 0; + char* full_url = NULL; + FILE* fp = NULL; + + if(!sftp || !local || !remote) + { + status = -1; + } + else if(!sftp->curl || !sftp->url) + { + status = -1; + } + + if(status == 0) + { + size_t len = strlen(remote) + strlen(sftp->url) + 1; + full_url = calloc(len, 1); + if(full_url) + { + snprintf(full_url, len, "%s%s", sftp->url, remote); + } + else + { + status = -1; + } + } + + if(status == 0) + { + fp = fopen(local, "w"); + if(!fp) + { + status = -1; + } + } + + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_UPLOAD, 0L); + } + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_WRITEDATA, fp); + } + if(status == 0) + { + status = curl_easy_setopt(sftp->curl, CURLOPT_URL, full_url); + } + if(status == 0) + { + status = curl_easy_perform(sftp->curl); + // TODO: have a timeout + for(int i = 0; (i < PSCP_SFTP_MAX_POLLING_ATTEMPTS) && (status == CURLE_REMOTE_FILE_NOT_FOUND); i++) + { + sleep(PSCP_SFTP_POLLING_DURATION); + status = curl_easy_perform(sftp->curl); + } + if(status == CURLE_LOGIN_DENIED) + { + fprintf(stderr, "%s rejected the provided credentials\n", sftp->url); + } + } + + if(fp) + { + fclose(fp); + fp = NULL; + } + if(full_url) + { + free(full_url); + full_url = NULL; + } + + return status; +} + +static void SignalHandler(int val) +{ + struct termios term; + tcgetattr(fileno(stdin), &term); + + term.c_lflag |= ECHO; + term.c_lflag &= ~ECHONL; + + tcsetattr(fileno(stdin), TCSAFLUSH, &term); + exit(-2); +} + +int GetPassword(char* result, size_t len, bool verbose) +{ + int status = 0; + bzero(result, len); + + struct sigaction newSa; + newSa.sa_handler = SignalHandler; + sigemptyset(&newSa.sa_mask); + newSa.sa_flags = SA_RESTART; + + struct sigaction oldSigInt; + struct sigaction oldSigTerm; + struct sigaction oldSigQuit; + + if(sigaction(SIGINT, &newSa, &oldSigInt) == -1) + { + if(verbose) fprintf(stderr, "ERROR: unable to change SIGINT handler\n"); + return -1; + } + if(sigaction(SIGTERM, &newSa, &oldSigTerm) == -1) + { + if(verbose) fprintf(stderr, "ERROR: unable to change SIGTERM handler\n"); + return -1; + } + if(sigaction(SIGQUIT, &newSa, &oldSigQuit) == -1) + { + if(verbose) fprintf(stderr, "ERROR: unable to change SIGQUIT handler\n"); + return -1; + } + + struct termios oldTerm, newTerm; + tcgetattr(fileno(stdin), &oldTerm); + + newTerm = oldTerm; + + newTerm.c_lflag &= ~ECHO; + newTerm.c_lflag |= ECHONL; + + tcsetattr(fileno(stdin), TCSAFLUSH, &newTerm); + + printf("NOTE: Try not to use a backspace...\n"); + printf("Key Passphrase: "); + fflush(stdout); + + char c = getchar(); + size_t i = 0; + + while(c != '\n' && c != '\f' && c != '\r') + { + if(i < len) + { + result[i] = c; + i++; + c = getchar(); + } + else + { + status = -1; + break; + } + } + tcsetattr(fileno(stdin), TCSANOW, &oldTerm); + + if(sigaction(SIGINT, &oldSigInt, NULL) == -1) + { + if(verbose) fprintf(stderr, "ERROR: unable to change SIGINT handler\n"); + status = -1; + } + if(sigaction(SIGTERM, &oldSigTerm, NULL) == -1) + { + if(verbose) fprintf(stderr, "ERROR: unable to change SIGTERM handler\n"); + status = -1; + } + if(sigaction(SIGQUIT, &oldSigQuit, NULL) == -1) + { + if(verbose) fprintf(stderr, "ERROR: unable to change SIGQUIT handler\n"); + status = -1; + } + + return status; +} + +void closeSftpSession(struct pscp_sftp_session* sftp) +{ + if(sftp) + { + if(sftp->curl) + { + curl_easy_cleanup(sftp->curl); + } + if(sftp->url) + { + free(sftp->url); + } + free(sftp); + } +} diff --git a/src/client/pscp_sftp.h b/src/client/pscp_sftp.h new file mode 100644 index 0000000..fb81e2f --- /dev/null +++ b/src/client/pscp_sftp.h @@ -0,0 +1,32 @@ +/* Copyright 2017 IBM Corp. + * + * 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. + */ + +#ifndef PSCP_SFTP_H +#define PSCP_SFTP_H + +#include <curl/curl.h> +#include <stdbool.h> + +struct pscp_sftp_session; + +int pscp_sftp_global_init(); +struct pscp_sftp_session* startSftpSession(const char * sftp_url, const char * privateKeyPath, bool verbose); +int sendFileToServer(const struct pscp_sftp_session* sftp, const char * local, const char * remote); +int getFileFromServer(const struct pscp_sftp_session* sftp, const char * local, const char * remote); +int pollOnFileFromServer(const struct pscp_sftp_session* sftp, const char * local, const char * remote); +void closeSftpSession(struct pscp_sftp_session* sftp); + +#endif diff --git a/src/client/pscp_sftp_test.c b/src/client/pscp_sftp_test.c new file mode 100644 index 0000000..b844184 --- /dev/null +++ b/src/client/pscp_sftp_test.c @@ -0,0 +1,67 @@ +/* Copyright 2017 IBM Corp. + * + * 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 <stdio.h> + +#include "pscp_sftp.h" + + +int main(int argc, char** argv) +{ + if((argc == 6) ) + { + const char* url = argv[1]; + const char* key = argv[2]; + const char* in = argv[3]; + const char* serverFile = argv[4]; + const char* out = argv[5]; + const bool verbose = true; + struct pscp_sftp_session* sftp = NULL; + + printf("Running SFTP global init\n"); + int status = pscp_sftp_global_init(); + if(status) + { + printf("Creating sftp session\n"); + sftp = startSftpSession(url, key, verbose); + } + if(sftp) + { + printf("Sending File\n"); + status = sendFileToServer(sftp, in, serverFile); + } + else + { + status = -1; + } + if(status == 0) + { + printf("Asking for File\n"); + status = getFileFromServer(sftp, out, serverFile); + } + if(sftp) + { + printf("Closing session...\n"); + closeSftpSession(sftp); + } + } + else + { + printf("USAGE:\n %s <url> <private-key-file> <local-in-file> <server-file> <local-out-file> [key-passphrase]\n", argv[0]); + } + + return 0; +} |

