From cd4a5891b8e2a019fdfda7512d6490e075628292 Mon Sep 17 00:00:00 2001 From: Jeffrey Armstrong Date: Tue, 29 Jun 2021 16:06:44 -0400 Subject: Tokens are now checked for file uploads. Fixed major buffer bugs in status reporting. --- captain/api.f90 | 26 ++++++---- captain/db.f90 | 20 ++++++++ captain/external.f90 | 16 +++++- captain/gemini.f90 | 15 +++++- captain/levitating-captain.prj | 3 ++ captain/queryutils.f90 | 12 +++++ captain/response.f90 | 2 +- captain/security.f90 | 107 +++++++++++++++++++++++++++++++++++++++++ common/protocol.f90 | 10 ++-- player/instructions.f90 | 6 +++ player/player.F90 | 8 ++- player/talking.f90 | 5 +- player/tasks.f90 | 15 ++++-- 13 files changed, 223 insertions(+), 22 deletions(-) create mode 100644 captain/security.f90 diff --git a/captain/api.f90 b/captain/api.f90 index 4d9545e..7b99ec7 100644 --- a/captain/api.f90 +++ b/captain/api.f90 @@ -182,6 +182,7 @@ contains use server_response use special_filenames use logging + use security, only: validate_token => validate_titan_token implicit none type(titan_request), intent(in)::req @@ -200,20 +201,25 @@ contains ! Task - "/api/player/{name}/job/{jobid}/task/{task num}" if(trim(req%component(2)) == "player" .and. & trim(req%component(4)) == "job" .and. & - trim(req%component(6)) == "task") then + trim(req%component(6)) == "task") & + then - job_id = req%path_component_int(5) + if(validate_token(req%token, req%component(3))) then + + job_id = req%path_component_int(5) - task_num = req%path_component_int(7) + task_num = req%path_component_int(7) - write(job_text, '(I6)') job_id - write(task_text, '(I6)') task_num - - call write_log("Handling a task update for job "//trim(job_text)//" task "//trim(task_text), LOG_INFO) - call handle_task_request(req) + write(job_text, '(I6)') job_id + write(task_text, '(I6)') task_num + + call write_log("Handling a task update for job "//trim(job_text)//" task "//trim(task_text), LOG_INFO) + call handle_task_request(req) + + fullpath => get_task_result_static_filename(job_id, task_num) + + end if - fullpath => get_task_result_static_filename(job_id, task_num) - end if if(associated(fullpath)) then diff --git a/captain/db.f90 b/captain/db.f90 index 1f9bc01..448c17d 100644 --- a/captain/db.f90 +++ b/captain/db.f90 @@ -146,6 +146,26 @@ contains end subroutine update_player_token_db + subroutine get_player_token_db(name, token) + implicit none + + character(*), intent(in)::name + character(*), intent(out)::token + type(sqlite3_stmt)::stmt + + token = " " + + if(stmt%prepare(db, "SELECT token FROM players WHERE name=?") == SQLITE_OK) then + if(stmt%bind_text(1, name) == SQLITE_OK ) then + if(stmt%step() == SQLITE_ROW) then + call stmt%column_text(0, token) + end if + end if + end if + call stmt%finalize() + + end subroutine get_player_token_db + function player_has_token_db(name) implicit none diff --git a/captain/external.f90 b/captain/external.f90 index edbe997..f5ba409 100644 --- a/captain/external.f90 +++ b/captain/external.f90 @@ -685,19 +685,33 @@ contains function external_request_titan(req) result(resp) use server_response use special_filenames + use query_utilities + use security use logging implicit none type(titan_request), intent(in)::req type(response)::resp + type(query)::q + character(len=:), pointer::fullpath character(12)::job_text, task_text integer::job_id, task_num + logical::proceed_to_create_filename + character(64)::msg - fullpath => get_full_filename_from_request(req) + fullpath => null() + + proceed_to_create_filename = .false. + + proceed_to_create_filename = validate_titan_token(req%token) + + if(proceed_to_create_filename) then + fullpath => get_full_filename_from_request(req) + end if if(associated(fullpath)) then diff --git a/captain/gemini.f90 b/captain/gemini.f90 index ff83554..6070dbe 100644 --- a/captain/gemini.f90 +++ b/captain/gemini.f90 @@ -32,6 +32,7 @@ contains subroutine read_request(ssl, req) use jessl, only: ssl_read use iso_c_binding + use logging implicit none type(c_ptr)::ssl @@ -44,8 +45,9 @@ contains req = " " i = 1 - + call sleep(1) bufread = ssl_read(ssl, buf) + do while(bufread > 0) do j = 1, bufread @@ -60,6 +62,11 @@ contains end do + if(j > bufread) then + j = bufread + end if + + if(buf(j) == c_new_line) then exit end if @@ -67,6 +74,8 @@ contains bufread = ssl_read(ssl, buf) end do + call write_log("Request Read: "//trim(req), LOG_DEBUG) + end subroutine read_request function read_into_buffer(unit_number, buffer) @@ -288,6 +297,7 @@ contains call req%path_component(1, first) if(trim(first) == 'api') then call write_log("API call encountered", LOG_DEBUG) + if(req%protocol == "gemini") then resp = api_request_gemini(req) call write_log("resp filename is: '"//trim(resp%body_filename)//"'", LOG_DEBUG) @@ -295,13 +305,16 @@ contains call treq%init_from_request(req, ssl) resp = api_request_titan(treq) end if + else + if(req%protocol == "gemini") then resp = external_request_gemini(req) else if(req%protocol == "titan") then call treq%init_from_request(req, ssl) resp = external_request_titan(treq) end if + end if call write_log("Handling response", LOG_DEBUG) diff --git a/captain/levitating-captain.prj b/captain/levitating-captain.prj index 392ac52..0e786f6 100644 --- a/captain/levitating-captain.prj +++ b/captain/levitating-captain.prj @@ -112,6 +112,9 @@ },{ "filename":"response.f90", "enabled":"1" + },{ + "filename":"security.f90", + "enabled":"1" },{ "filename":"special.f90", "enabled":"1" diff --git a/captain/queryutils.f90 b/captain/queryutils.f90 index 550a2f3..00a914b 100644 --- a/captain/queryutils.f90 +++ b/captain/queryutils.f90 @@ -48,6 +48,7 @@ implicit none procedure :: component_count => query_component_count procedure :: get_value_by_index => get_query_value_from_index procedure :: get_value_by_key => get_query_value_from_key + procedure :: has_key => query_has_key generic, public :: get_value => get_value_by_index, get_value_by_key @@ -240,4 +241,15 @@ contains end function get_query_value_from_key + function query_has_key(self, key) + implicit none + + class(query), intent(in)::self + character(*), intent(in)::key + logical::query_has_key + + query_has_key = associated(self%get_value_by_key(key)) + + end function query_has_key + end module query_utilities diff --git a/captain/response.f90 b/captain/response.f90 index e743d0d..b091a42 100644 --- a/captain/response.f90 +++ b/captain/response.f90 @@ -503,7 +503,7 @@ contains if(j<=0) then j = n else - j = j+i + j = j+i-1 end if i = i + label_width + 2 diff --git a/captain/security.f90 b/captain/security.f90 new file mode 100644 index 0000000..2f5fa4c --- /dev/null +++ b/captain/security.f90 @@ -0,0 +1,107 @@ +! Copyright (c) 2021 Approximatrix, LLC +! +! Permission is hereby granted, free of charge, to any person obtaining a copy +! of this software and associated documentation files (the "Software"), to deal +! in the Software without restriction, including without limitation the rights +! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +! copies of the Software, and to permit persons to whom the Software is +! furnished to do so, subject to the following conditions: +! +! The above copyright notice and this permission notice shall be included in +! all copies or substantial portions of the Software. +! +! The Software shall be used for Good, not Evil. +! +! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +! SOFTWARE. + +module security +implicit none + +contains + + function validate_titan_token(token_string, player_name) + use captain_db + use logging + implicit none + + character(*), intent(in)::token_string + character(*), intent(in), optional::player_name + logical::validate_titan_token + character(len=:), pointer::dbtoken, internal_player + + integer::player_id, i, j + + validate_titan_token = .false. + + i = index(token_string, ":") + j = len_trim(token_string) + + if(present(player_name)) then + + allocate(character(len=len_trim(player_name)) :: internal_player) + internal_player = player_name + + else + + if(i > 0) then + allocate(character(len=i-1) :: internal_player) + internal_player = token_string(1:i-1) + end if + + end if + + if(associated(internal_player)) then + + player_id = get_player_id(internal_player) + if(player_id >= 0) then + + if(.not. player_has_token_db(internal_player)) then + + validate_titan_token = .true. + + else + + allocate(character(len=(len_trim(token_string) + 1)) :: dbtoken) + dbtoken = " " + + call get_player_token_db(internal_player, dbtoken) + + if(i <= 0) then + i = 1 + else + i = i + 1 + end if + + call write_log("Tokens '"//trim(dbtoken)//"' vs. '"//token_string(i:j)//"'") + + validate_titan_token = (trim(dbtoken) == token_string(i:j)) + + deallocate(dbtoken) + + end if + + end if + + if(validate_titan_token) then + call write_log("Titan token valid for "//trim(internal_player)) + else + call write_log("Titan token FAILURE for "//trim(internal_player)) + end if + + deallocate(internal_player) + + else + + call write_log("Titan token did not include a player - REJECTED") + + end if + + end function validate_titan_token + +end module security \ No newline at end of file diff --git a/common/protocol.f90 b/common/protocol.f90 index cf93b0d..25050cb 100644 --- a/common/protocol.f90 +++ b/common/protocol.f90 @@ -151,8 +151,8 @@ contains response_line_index = 0 bytes_received = retrieve_characters(conn%ssl, buffer) - do while(bytes_received > 0) + do while(bytes_received > 0) do i=1, bytes_received if(.not. response_line_completed) then response_line_index = response_line_index + 1 @@ -286,14 +286,17 @@ contains !Print *, "bytes read for sending: ", bytes_read - do while(bytes_read > 0) + bytes_written = 0 + do while(bytes_read > 0 .and. bytes_written >= 0) bytes_written = ssl_write(conn%ssl, buffer(1:bytes_read)) total_written = total_written + bytes_written bytes_read = read_into_buffer(unit_number, buffer) !Print *, "bytes read for sending now: ", bytes_read, " and so far, we wrote", total_written end do - if(total_written >= file_length) then + if(total_written >= file_length .and. bytes_written >= 0) then + + Print *, "Upload ok: ", total_written, file_length response_line_completed = .false. response_line = " " @@ -321,6 +324,7 @@ contains end do else + Print *, "Marking localfail" returncode = STATUS_LOCALFAIL end if diff --git a/player/instructions.f90 b/player/instructions.f90 index 4669f18..65b392d 100644 --- a/player/instructions.f90 +++ b/player/instructions.f90 @@ -381,7 +381,10 @@ contains end if if(get_task_failure_okay(j, i) .and. .not. success) then + Print *, "Reseting to failure" success = .true. + else if(.not. success) then + Print *, "Task failure" end if end function perform_task @@ -411,6 +414,7 @@ contains do i = 1, task_count call get_status_url(job_id, i, url, status=STATUS_STARTING) + Print *, "Reporting: "//trim(url) server_status = request_to_ignored(url) res = perform_task(j, i, captured_filename) @@ -444,9 +448,11 @@ contains else call get_status_url(job_id, i, url, status=STATUS_FAILED) server_status = request_to_ignored(url) + Print *, "Attempted reporting failure" exit endif end if + end do call get_job_report_url(job_id, res, url) diff --git a/player/player.F90 b/player/player.F90 index 121b441..2233aff 100644 --- a/player/player.F90 +++ b/player/player.F90 @@ -71,6 +71,7 @@ implicit none call get_instruction_name_from_checkin(j_checkin, instruction_name) call get_instruction_url(instruction_name, url) + instr_json_available = request_json(url, j_instructions) if(instr_json_available) then @@ -117,7 +118,8 @@ contains Print *, " -h Display this help" Print *, " -w Use dir as the working directory" Print *, " -l Use log as the logfile" - Print *, " -i This players identity" + Print *, " -i This player's identity" + Print *, " -t This player's security token" end subroutine usage @@ -158,6 +160,10 @@ contains else if(trim(option) == "-i") then i = i + 1 call get_command_argument(i, identity) + + else if(trim(option) == "-t") then + i = i + 1 + call get_command_argument(i, token) end if diff --git a/player/talking.f90 b/player/talking.f90 index 42b6451..2171f41 100644 --- a/player/talking.f90 +++ b/player/talking.f90 @@ -94,6 +94,7 @@ contains open(newunit=io, status="scratch", access='stream') status_code = request_url(mod_url, io, return_type) + Print *, status_code close(io) deallocate(mod_url) @@ -102,7 +103,7 @@ contains function send_file(url, filename) result(status_code) use gemini_protocol - use config, only: token + use config, only: token, identity implicit none character(*), intent(in)::url @@ -125,7 +126,7 @@ contains access='STREAM', form='UNFORMATTED', iostat=istatus) if(istatus == 0) then - status_code = titan_post_url(mod_url, unit_number, file_size, token) + status_code = titan_post_url(mod_url, unit_number, file_size, trim(identity)//":"//trim(token)) close(unit_number) else status_code = STATUS_LOCALFAIL diff --git a/player/tasks.f90 b/player/tasks.f90 index e0eecfd..b0f2cc1 100644 --- a/player/tasks.f90 +++ b/player/tasks.f90 @@ -118,7 +118,7 @@ contains allocate(character(len=len_trim(mask)+DIR_LIST_STRING_LENGTH+1) :: fullname) call path_from_file(mask, dir) - allocate(statuses(size(files))) + allocate(statuses(size(files))) do i = 1, size(files) ! On some systems, we're getting subdirectories back... @@ -143,11 +143,15 @@ contains res = .false. end if + + if(.not. res) then + Print *, "Glob failure" + end if end function upload_glob function upload(url, source_filename) result(res) - use config, only: token, captain + use config, only: token, captain, identity use gemini_protocol, only: titan_post_url, STATUS_SUCCESS implicit none @@ -205,7 +209,8 @@ contains if(istatus == 0) then Print *, "Writing "//trim(mod_url) - istatus = titan_post_url(mod_url, unit_number, file_size, token) + istatus = titan_post_url(mod_url, unit_number, file_size, trim(identity)//":"//trim(token)) + Print *, "Response code from server: ", istatus res = (istatus == STATUS_SUCCESS) close(unit_number) else @@ -214,6 +219,10 @@ contains deallocate(mod_url) + if(.not. res) then + Print *, "Upload Failure" + end if + end function upload function download(url, destination_filename) -- cgit v1.2.3