X-Git-Url: http://git.cascardo.eti.br/?a=blobdiff_plain;f=rnetclient.c;h=946fa7956f03c1910855dbe2522c3d7557f810be;hb=0a6a048d958808d175079d66b336c248c0195c47;hp=aa650557a14a973b18479c9781e730bebe49d5d3;hpb=2861a00f694d7322e1b2fd60ead0db8c39699307;p=cascardo%2Frnetclient.git diff --git a/rnetclient.c b/rnetclient.c index aa65055..946fa79 100644 --- a/rnetclient.c +++ b/rnetclient.c @@ -1,5 +1,7 @@ /* - * Copyright (C) 2012-2013 Thadeu Lima de Souza Cascardo + * Copyright (C) 2012-2014 Thadeu Lima de Souza Cascardo + * Copyright (C) 2014 Alexandre Oliva + * Copyright (C) 2014 Sergio Durigan Junior * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,10 +18,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _GNU_SOURCE #include #include #include #include +#include +#include +#include #include #include #include @@ -44,21 +50,31 @@ static const char rnetclient_doc[] = "Send the Brazilian Income Tax Report to the Brazilian " "Tax Authority"; static const char rnetclient_args_doc[] = - "[-d|--declaration] FILE"; + "[-d|--declaration] FILE [-o|--output-dir DIRECTORY]"; /* Description and definition of each option accepted by the program. */ static const struct argp_option rnetclient_options_desc[] = { - { "declaration", 'd', "FILE", OPTION_ARG_OPTIONAL, + { "declaration", 'd', "FILE", 0, "The Income Tax Report file that will be sent.", 0 }, + { "output-dir", 'o', "DIRECTORY", 0, + "The directory where you wish to save the receipt.", + 0 }, + { NULL }, }; struct rnetclient_args { /* File representing the declaration. */ char *input_file; + + /* Output directory to save the receipt. */ + char *output_dir; + + /* Output filename. */ + char output_file[PATH_MAX]; }; /* Parser for command line arguments. */ @@ -73,6 +89,10 @@ static error_t rnetclient_parse_opt(int key, char *arg, struct argp_state *state a->input_file = arg; break; + case 'o': + a->output_dir = arg; + break; + case ARGP_KEY_ARG: /* The user has possibly provided a filename without using any switches (e.g., by running './rnetclient @@ -138,9 +158,9 @@ static int deflateRecord(char *buffer, size_t len, char **out, size_t *olen, int deflateEnd(&zstrm); return -1; } - zstrm.next_in = buffer; + zstrm.next_in = (z_const Bytef *) buffer; zstrm.avail_in = len; - zstrm.next_out = *out + 6; + zstrm.next_out = (Bytef *) *out + 6; zstrm.avail_out = len * 2 + 30; while ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END && zstrm.avail_out > 0); @@ -169,15 +189,15 @@ static int inflateRecord(char *buffer, size_t len, char **out, size_t *olen) zstrm.opaque = Z_NULL; if ((r = inflateInit(&zstrm)) != Z_OK) return -1; - *olen = chars2len(buffer+3); + *olen = chars2len((unsigned char *) buffer+3); *out = malloc(*olen); if (!out) { inflateEnd(&zstrm); return -1; } - zstrm.next_in = buffer + 6; + zstrm.next_in = (z_const Bytef *) buffer + 6; zstrm.avail_in = len - 6; - zstrm.next_out = *out; + zstrm.next_out = (Bytef *) *out; zstrm.avail_out = *olen; while ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END && zstrm.avail_out > 0); @@ -197,10 +217,8 @@ static int connect_rnet(int *c) struct addrinfo *addresses; struct addrinfo *addr; struct addrinfo hint; - struct sockaddr_in saddr; int r; int fd = *c = -1; - int i; memset(&hint, 0, sizeof(hint)); hint.ai_family = AF_UNSPEC; hint.ai_socktype = SOCK_STREAM; @@ -262,7 +280,7 @@ static int rnet_send(gnutls_session_t session, char *buffer, size_t len, int hea The uploaded file may be larger: to upload such large files, it suffices to send multiple records till the entire file is transferred, without waiting for a response. Since - we've alread informed the server of the file size in the + we've already informed the server of the file size in the header, it knows exactly how much data to expect before sending a response. It will only send an error message before that if it times us out. @@ -275,7 +293,7 @@ static int rnet_send(gnutls_session_t session, char *buffer, size_t len, int hea This chunk size worked at the first try, uploading a ~100KiB file, so let's stick with it. */ - const int maxc = 64472; + const unsigned int maxc = 64472; if (header && len > maxc) return -1; @@ -298,19 +316,18 @@ static int rnet_send(gnutls_session_t session, char *buffer, size_t len, int hea static int rnet_recv(gnutls_session_t session, struct rnet_message **message) { - char *out; - size_t olen; - int r; + char *out = NULL; + size_t olen = 0; char *buffer; size_t len; rnet_message_expand(message, 6); buffer = (*message)->buffer; - r = gnutls_record_recv(session, buffer, 6); + gnutls_record_recv(session, buffer, 6); if (buffer[0] == 0x01) { - len = chars2len(buffer+1); + len = chars2len((unsigned char *) buffer+1); rnet_message_expand(message, len); buffer = (*message)->buffer + 6; - r = gnutls_record_recv(session, buffer, len); + gnutls_record_recv(session, buffer, len); inflateRecord(buffer - 6, len + 6, &out, &olen); rnet_message_del(*message); *message = NULL; @@ -319,68 +336,89 @@ static int rnet_recv(gnutls_session_t session, struct rnet_message **message) (*message)->len = olen; free(out); } else { - len = chars2len(buffer+1); + len = chars2len((unsigned char *) buffer+1); rnet_message_expand(message, len - 1); buffer = (*message)->buffer + 6; - r = gnutls_record_recv(session, buffer, len - 1); + gnutls_record_recv(session, buffer, len - 1); (*message)->len = len + 4; rnet_message_strip(*message, 4); } return 0; } -static void save_rec_file(char *cpf, char *buffer, int len) +static int open_rec_file(char *cpf, struct rnetclient_args *args) { int fd; - char *filename; - char *home, *tmpdir; - mode_t mask; - size_t fnlen; - int r; - home = getenv("HOME"); - if (!home) { - tmpdir = getenv("TMPDIR"); - if (!tmpdir) - tmpdir = "/tmp"; - home = tmpdir; + char *path, *tmp; + + path = args->output_dir; + + /* Now it's time to decide which filename to write. We use + the declaration's filename as a base layout, because the + proprietary version of the IRPF program only recognizes + receipts if they have the same name as the declaration + files (disconsidering the extensions). For example, if the + declaration file is named "123.DEC", the receipt should be + named "123.REC". Therefore, if the declaration file has + the ".DEC" extension, we strip it out and add the ".REC". + Otherwise, we use the default template, which is to save + the receipt with the name "$CPF.REC". */ + tmp = strstr(args->input_file, ".DEC"); + if (tmp != NULL && tmp[sizeof(".DEC") - 1] == '\0') { + char *p; + /* We found the ".REC" extension. */ + p = strdup(basename(args->input_file)); + /* Replacing the ".DEC" by ".REC". Fortunately, we + just have to change one letter. */ + tmp = strstr(p, ".DEC"); + tmp[1] = 'R'; + snprintf(args->output_file, PATH_MAX, "%s/%s", path, p); + free(p); + } else { + /* The declaration filename does not follow the + convention, so we will not use it as a template. + We just generate a filename using "$CPF.REC". */ + snprintf(args->output_file, PATH_MAX, "%s/%s.REC", path, cpf); } - fnlen = strlen(home) + strlen(cpf) + 13; - filename = malloc(fnlen); - snprintf(filename, fnlen, "%s/%s.REC.XXXXXX", home, cpf); - mask = umask(0177); - fd = mkstemp(filename); + /* Now, open the file and write. */ + fd = open(args->output_file, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR); if (fd < 0) { - fprintf(stderr, "Could not create receipt file: %s\n", - strerror(errno)); - goto out; + fprintf(stderr, "Could not create receipt file \"%s\": %s\n", + args->output_file, strerror(errno)); } - r = write(fd, buffer, len); + return fd; +} + +static int save_rec_file(int fd, char *buffer, int len, const struct rnetclient_args *args) +{ + ssize_t r; + + do { + r = write(fd, buffer, len); + } while (r < 0 && errno == EAGAIN); if (r != len) { - fprintf(stderr, "Could not write to receipt file%s%s\n", - r < 0 ? ": " : ".", - r < 0 ? strerror(errno) : ""); - goto out; + fprintf(stderr, "Could not write to receipt file: %s", + strerror(errno)); + } else { + fprintf(stderr, "Wrote the receipt file to %s.\n", + args->output_file); } - fprintf(stderr, "Wrote the receipt to %s.\n", filename); -out: - close(fd); - free(filename); - umask(mask); + return r; } -static void handle_response_text_and_file(char *cpf, struct rnet_message *message) +static void handle_response_text_and_file(int fd, struct rnet_message *message, const struct rnetclient_args *args) { char *value; int vlen; if (!rnet_message_parse(message, "texto", &value, &vlen)) fprintf(stderr, "%.*s\n", vlen, value); if (!rnet_message_parse(message, "arquivo", &value, &vlen)) - save_rec_file(cpf, value, vlen); + save_rec_file(fd, value, vlen, args); } -static void handle_response_already_found(char *cpf, struct rnet_message *message) +static void handle_response_already_found(int fd, struct rnet_message *message, const struct rnetclient_args *args) { - handle_response_text_and_file(cpf, message); + handle_response_text_and_file(fd, message, args); } static void handle_response_error(struct rnet_message *message) @@ -395,7 +433,7 @@ static void handle_response_error(struct rnet_message *message) int main(int argc, char **argv) { int c; - int r; + int r = 0; struct rnet_decfile *decfile; struct rnet_message *message = NULL; struct rnetclient_args rnet_args; @@ -403,6 +441,8 @@ int main(int argc, char **argv) int finish = 0; char *cpf; error_t err; + char cwd[PATH_MAX]; + int outfd; /* Parsing the command line arguments. The argp_parse function calls exit() if there is some error during the @@ -417,6 +457,25 @@ int main(int argc, char **argv) if (err != 0) fprintf(stderr, "internal error while parsing command line arguments."); + /* If the user provided the output directory where she wishes + to save the receipt, then we use it. Otherwise, we save + the file in the current working directory (CWD). */ + if (rnet_args.output_dir == NULL) { + rnet_args.output_dir = getcwd(cwd, PATH_MAX); + } else { + struct stat st; + if (stat(rnet_args.output_dir, &st) < 0) { + fprintf(stderr, "Could not stat directory \"%s\": %s\n", + rnet_args.output_dir, strerror(errno)); + exit(1); + } + if (!S_ISDIR(st.st_mode)) { + fprintf(stderr, "Error: \"%s\" is a not a directory.\n", + rnet_args.output_dir); + exit(1); + } + } + decfile = rnet_decfile_open(rnet_args.input_file); if (!decfile) { fprintf(stderr, "could not parse file \"%s\": %s\n", rnet_args.input_file, strerror(errno)); @@ -425,6 +484,12 @@ int main(int argc, char **argv) cpf = rnet_decfile_get_header_field(decfile, "cpf"); + outfd = open_rec_file(cpf, &rnet_args); + if (outfd < 0) { + r = 1; + goto out_rec; + } + gnutls_global_init(); session_new(&session); @@ -432,18 +497,29 @@ int main(int argc, char **argv) if (r) { fprintf(stderr, "error connecting to server: %s\n", r == EAI_SYSTEM ? strerror(errno) : gai_strerror(r)); - exit(1); + r = 1; + goto out_connect; } gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(intptr_t) c); r = handshake(c); if (r < 0) { - exit(1); + r = 1; + goto out_handshake; } - if ((r = gnutls_handshake(session)) < 0) + if ((r = gnutls_handshake(session)) < 0) { fprintf(stderr, "error in handshake: %s\n", gnutls_strerror(r)); + r = 1; + goto out_handshake; + } + + r = rnet_encode(decfile, &message); + if (r < 0) { + fprintf(stderr, "error encoding message, file not supported?\n"); + r = 1; + goto out; + } - rnet_encode(decfile, &message); rnet_send(session, message->buffer, message->len, 1); rnet_message_del(message); @@ -451,23 +527,24 @@ int main(int argc, char **argv) r = rnet_recv(session, &message); if (r || !message || message->len == 0) { fprintf(stderr, "error when receiving response\n"); + r = 1; goto out; } switch (message->buffer[0]) { case 1: /* go ahead */ - handle_response_text_and_file(cpf, message); + handle_response_text_and_file(outfd, message, &rnet_args); break; case 3: /* error */ handle_response_error(message); finish = 1; break; case 4: - handle_response_already_found(cpf, message); + handle_response_already_found(outfd, message, &rnet_args); finish = 1; break; case 2: case 5: - handle_response_text_and_file(cpf, message); + handle_response_text_and_file(outfd, message, &rnet_args); finish = 1; break; } @@ -483,6 +560,7 @@ int main(int argc, char **argv) r = rnet_recv(session, &message); if (r || !message || message->len == 0) { fprintf(stderr, "error when receiving response\n"); + r = 1; goto out; } switch (message->buffer[0]) { @@ -493,15 +571,19 @@ int main(int argc, char **argv) case 4: case 5: case 1: - handle_response_text_and_file(cpf, message); + handle_response_text_and_file(outfd, message, &rnet_args); break; } out: gnutls_bye(session, GNUTLS_SHUT_RDWR); +out_handshake: close(c); - rnet_decfile_close(decfile); +out_connect: gnutls_global_deinit(); + close(outfd); +out_rec: + rnet_decfile_close(decfile); - return 0; + return r; }