/* * files.c * * Code used to send and receive files across cutlass. * * Copyright (c) 2004 Todd MacDermid * */ #include #include #include #include #include #include #include #include #include #include #include #include /* * file_init_out initializes an already allocated file_transit_info * structure, preparing it to transfer the file in . It * will open a needed file descriptor and return the information * from the file. */ int file_init_out(cutlass_t *cut_handle, struct file_transit_info *file_info, uint8_t *filename) { int fd; fd = open(filename, O_RDONLY); if(fd < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "file_init_out: file open failed\n"); return(-1); } strncpy(file_info->name, filename, CUTLASS_FILENAME_LEN-1); file_info->name[CUTLASS_FILENAME_LEN-1] = 0; file_info->fd = fd; file_hash(fd, file_info->file_csum); return(0); } /* * file_init_in initializes an already allocated file_transit_info * structure, preparing it handle receiving the file info from * the file offered by . The receiving program * should have set the desired file name/location to write to in * . File descriptor is opened in cutlass_file_accept(). */ int file_init_in(cutlass_t *cut_handle, struct file_transit_info *file_info, uint8_t *filename, struct cutlass_file_init_hdr *file_init_hdr) { memcpy(file_info->file_csum, file_init_hdr->file_csum, CUT_CSUM_LEN); strncpy(file_info->name, file_init_hdr->file_name, CUTLASS_FILENAME_LEN-1); file_info->name[CUTLASS_FILENAME_LEN-1] = 0; return(0); } /* * file_info_init allocates a new file_info structure, and fills it * with basic defaults. Will call either file_init_in or file_init_out * to actually open the file descriptor and fill out some specifics. * * Returns the file_transit_info structure pointer on success, or NULL * on failure. */ struct file_transit_info * file_info_init(cutlass_t *cut_handle, conn_t *conn, int direction, uint8_t *filename, struct cutlass_file_init_hdr *file_init_hdr) { struct file_transit_info *new_file; new_file = calloc(1, sizeof(struct file_transit_info)); if(new_file == NULL) { cutlass_sysmsg(cut_handle, CUT_ERROR, "file_info_init: could not allocate memory\n"); return(NULL); } if(filename == NULL) { cutlass_sysmsg(cut_handle, CUT_ERROR, "file_info_init: opening a NULL filename\n"); goto fail; } if(direction == CUT_IN) { if(file_init_hdr == NULL) { cutlass_sysmsg(cut_handle, CUT_ERROR, "file_info_init: opening with NULL initial packet\n"); goto fail; } if(file_init_in(cut_handle, new_file, filename, file_init_hdr)<0){ cutlass_sysmsg(cut_handle, CUT_ERROR, "file_info_init: file_init_in failed\n"); goto fail; } } else if(direction == CUT_OUT) { if(file_init_out(cut_handle, new_file, filename) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "file_info_init: file_init_out failed\n"); goto fail; } } else { cutlass_sysmsg(cut_handle, CUT_ERROR, "file_info_init: direction not set\n"); goto fail; } return(new_file); fail: free(new_file); return(NULL); } /* * cutlass_file_offer is the API exposed to offer a file to a remote * host. This can also be called if we serve a file requested by the * remote side. Allocates a file object and a channel slot for the * file traffic, and sends the initial file_init packet. * * Returns the channel_id on success, -1 on failure. */ int cutlass_file_offer(cutlass_t *cut_handle, uint8_t *fingerprint, char *filename) { conn_t *conn; struct channel *transport_info; struct file_transit_info *file_info; struct stat stat_buf; struct timespec now; uint8_t channel_id; if((NULL == cut_handle) || (NULL == fingerprint) || (NULL == filename)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_offer: Passed a NULL handle\n"); return(-1); } conn = hashtable_fingerprint_find(cut_handle, fingerprint); if(NULL == conn) { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_file_offer: Couldn't find connection\n"); return(-1); } if(!(conn->capabilities & CAN_RECV_FILES)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_offer: Remote host does not accept files\n"); pthread_mutex_unlock(&(conn->conn_mutex)); return(-1); } if(cutlass_time(&now) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_offer: Could not get current time\n"); pthread_mutex_unlock(&(conn->conn_mutex)); return(-1); } file_info = file_info_init(cut_handle, conn, CUT_OUT, filename, NULL); if(file_info == NULL) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_offer: file_info_init failed\n"); pthread_mutex_unlock(&(conn->conn_mutex)); return(-1); } if(fstat(file_info->fd, &stat_buf) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_offer: file stat failed\n"); file_info_destroy(cut_handle, file_info); pthread_mutex_unlock(&(conn->conn_mutex)); return(-1); } channel_id = new_cut_channel_out(cut_handle, conn, CHANNEL_FILE, stat_buf.st_size, file_info, NULL, 0); if(channel_id == 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_offer: Could not allocate new channel\n"); file_info_destroy(cut_handle, file_info); pthread_mutex_unlock(&(conn->conn_mutex)); return(-1); } transport_info = &(conn->out_channels[channel_id]); /* transport_info->data_size = stat_buf.st_size; */ cutlass_transport_init_send(cut_handle, conn, transport_info, &now); pthread_mutex_unlock(&(conn->conn_mutex)); return(channel_id + CUT_NUM_CHANNELS); } /* * cutlass_file_offer_all is a mechanism to offer a file to every other * connected person. Mainly used for testing file transfer in text_cutlass, * I'm not sure this will stick around. */ int cutlass_file_offer_all(cutlass_t *cut_handle, char *filename) { uint8_t *array_loc; uint8_t conn_array[CUT_ID_LEN * CUT_HK_ARRAY_SZ]; int i; int num_conn; int retval; num_conn = cutlass_conn_list(cut_handle, conn_array, CUT_HK_ARRAY_SZ); array_loc = conn_array; for (i = 0; i < num_conn; i++) { retval = cutlass_file_offer(cut_handle, array_loc, filename); if(retval < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_offer_all: Received fatal error\n"); return(-1); } array_loc += CUT_ID_LEN; } return(0); } int cutlass_file_request_process(cutlass_t *cut_handle, conn_t *conn, request_t *file_request) { return(0); } /* * cutlass_file_cancel_recv terminates the file receive process. It is * either called from the transport layer, or from cutlass_file_reject. * * Returns 0 on success, or -1 on failure */ int cutlass_file_cancel_recv(cutlass_t *cut_handle, conn_t *conn, uint8_t channel_id) { struct channel *transport_info; struct file_transit_info *file_info; struct timespec now; if((NULL == cut_handle) || (NULL == conn)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_start_recv: Passed a NULL handle\n"); return(-1); } transport_info = &(conn->in_channels[channel_id]); if(transport_info->channel_type == CHANNEL_FILE) { file_info = transport_info->channel_info; } else { cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_file_cancel_recv: " "Asked to cancel non-file channel\n"); del_cut_channel(cut_handle, conn, CUT_IN, channel_id); // pthread_mutex_unlock(&(conn->conn_mutex)); return(1); } if(cutlass_time(&now) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_cancel: Could not get current time\n"); // pthread_mutex_unlock(&(conn->conn_mutex)); return(1); } transport_info->channel_state = CUT_CHANNEL_STATE_FINISH; empty_gaplist(transport_info->gap_list); cutlass_transport_ack_send(cut_handle, conn, transport_info, NULL, &now); return(0); } /* * cutlass_file_start_recv begins the file receive process. It is * either called from the transport layer, or from cutlass_file_accept. * * Returns 0 on success, or -1 on failure */ int cutlass_file_start_recv(cutlass_t *cut_handle, conn_t *conn, uint8_t channel_id, char *file_name, int overwrite) { int fd; struct timespec now; struct file_transit_info *file_info; if((NULL == cut_handle) || (NULL == conn) || (NULL == file_name)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_start_recv: Passed a NULL handle\n"); return(-1); } if(conn->in_channels[channel_id].channel_type == CHANNEL_FILE) { file_info = conn->in_channels[channel_id].channel_info; } else { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_file_start_recv:" "received packet on non-file channel\n"); del_cut_channel(cut_handle, conn, CUT_IN, channel_id); return(1); } if(cutlass_time(&now) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_start_recv: Could not get current time\n"); cutlass_file_cancel_recv(cut_handle, conn, channel_id); return(1); } if(overwrite) { fd = open(file_name, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); } else { fd = open(file_name, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); } if(fd < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_start_recv: Could not create file %s\n", file_name); cutlass_file_cancel_recv(cut_handle, conn, channel_id); return(1); } file_info->fd = fd; conn->in_channels[channel_id].channel_state = CUT_CHANNEL_STATE_TRANSFERRING; cutlass_transport_ack_send(cut_handle, conn, &conn->in_channels[channel_id], NULL, &now); return(0); } /* * cutlass_file_accept is called to indicate acceptance of a file * transfer. It should be called by an action handler. * * Returns 0 on success, or -1 on failure */ int cutlass_file_accept(cutlass_t *cut_handle, struct cut_action_obj *action_obj, char *local_name) { conn_t *conn; int retval; uint8_t channel_id; if((NULL == cut_handle) || (NULL == action_obj)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_accept: Passed a NULL handle\n"); return(-1); } conn = hashtable_fingerprint_find(cut_handle, action_obj->conn_fingerprint); if(NULL == conn) { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_file_offer: Couldn't find connection\n"); return(-1); } channel_id = action_obj->external_channel_id; if(NULL == local_name) { retval = cutlass_file_start_recv(cut_handle, conn, channel_id, action_obj->file_name, 0); } else { retval = cutlass_file_start_recv(cut_handle, conn, channel_id, local_name, 1); } pthread_mutex_unlock(&(conn->conn_mutex)); return(retval); } /* * cutlass_file_reject is a way for the action handler to signal that * the proffered file will not be accepted 'round these parts here. */ int cutlass_file_reject(cutlass_t *cut_handle, struct cut_action_obj *action_obj) { conn_t *conn; int retval; uint8_t channel_id; if((NULL == cut_handle) || (NULL == action_obj)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_reject: Passed a NULL pointer\n"); return(-1); } conn = hashtable_fingerprint_find(cut_handle, action_obj->conn_fingerprint); if(NULL == conn) { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_file_reject: Couldn't find connection\n"); return(-1); } channel_id = action_obj->external_channel_id; retval = cutlass_file_cancel_recv(cut_handle, conn, channel_id); pthread_mutex_unlock(&(conn->conn_mutex)); return(retval); } /* * file_info_destroy safely deallocates a file_transit_info structure, * deallocating substructures within and closing file descriptors * before freeing */ void file_info_destroy(cutlass_t *cut_handle, struct file_transit_info *file_info) { if((NULL == cut_handle) || (NULL == file_info)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "file_info_destroy: Passed a NULL handle\n"); } else { if(file_info->fd > 0) { close(file_info->fd); } free(file_info); } } /* * file_init_pack takes a cutlass_file_init_hdr structure and writes * the contents out in network byte order to a packet buffer. * Returns 0 on success, -1 on error. */ int file_init_pack(cutlass_t *cut_handle, uint8_t *packet, struct cutlass_file_init_hdr *header) { uint8_t *packet_loc; uint16_t tmp_name_len; if((packet == NULL) || (header == NULL)) return(-1); if(header->file_name_len >= CUTLASS_FILENAME_LEN) return(-1); packet_loc = packet; memcpy(packet_loc, header->file_csum, CUT_CSUM_LEN); packet_loc += CUT_CSUM_LEN; tmp_name_len = htons(header->file_name_len); memcpy(packet_loc, &tmp_name_len, 2); packet_loc += 2; strncpy(packet_loc, header->file_name, CUTLASS_FILENAME_LEN); return(0); } /* * file_init_unpack takes in an unencrypted packet buffer containing * a file initialization header, and unpacks the contents into * a cutlass_file_init_hdr structure. Returns 0 on success, or * -1 on error. */ int file_init_unpack(cutlass_t *cut_handle, uint8_t *packet, struct cutlass_file_init_hdr *header) { uint8_t *packet_loc; uint16_t tmp_name_len; if((packet == NULL) || (header == NULL)) return(-1); memset(header, 0, sizeof(struct cutlass_file_init_hdr)); packet_loc = packet; memcpy(header->file_csum, packet_loc, CUT_CSUM_LEN); packet_loc += CUT_CSUM_LEN; memcpy(&tmp_name_len, packet_loc, 2); header->file_name_len = ntohs(tmp_name_len); packet_loc += 2; if(header->file_name_len >= CUTLASS_FILENAME_LEN) { cutlass_sysmsg(cut_handle, CUT_INFO, "file_init_unpack: Name length too long\n"); return(-1); } strncpy(header->file_name, packet_loc, header->file_name_len); header->file_name[CUTLASS_FILENAME_LEN - 1] = 0; return(0); } int cutlass_file_action_populate(cutlass_t *cut_handle, conn_t *conn, struct file_transit_info *file_info, struct channel *transport_info, struct cut_action_obj *action_obj, int action_type, int direction) { if((NULL == cut_handle) || (NULL == conn) || (NULL == file_info) || (NULL == transport_info) || (NULL == action_obj)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_action_populate: Passed a NULL pointer\n"); return(-1); } cutlass_action_populate(cut_handle, conn, action_obj); action_obj->action_type = action_type; strncpy(action_obj->file_name, file_info->name, CUTLASS_FILENAME_LEN); action_obj->file_size = transport_info->data_size; memcpy(action_obj->file_csum, file_info->file_csum, CUT_CSUM_LEN); if(direction == CUT_IN) { action_obj->external_channel_id = transport_info->channel_id; } else { action_obj->external_channel_id = transport_info->channel_id + CUT_NUM_CHANNELS; } return(0); }