2 * Copyright (C) 2012-2014 Thadeu Lima de Souza Cascardo <cascardo@minaslivre.org>
3 * Copyright (C) 2014 Alexandre Oliva <lxoliva@fsfla.org>
4 * Copyright (C) 2014 Sergio Durigan Junior <sergiodj@sergiodj.net>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
34 #include <gnutls/gnutls.h>
39 #include "rnet_message.h"
40 #include "rnet_encode.h"
42 /* Program version and bug report address. */
44 const char *argp_program_version = PACKAGE_VERSION;
45 const char *argp_program_bug_address = PACKAGE_BUGREPORT;
47 /* Documentation strings. */
49 static const char rnetclient_doc[] =
50 "Send the Brazilian Income Tax Report to the Brazilian "
52 static const char rnetclient_args_doc[] =
53 "[-d|--declaration] FILE [-o|--output-dir DIRECTORY]"
54 " [-s|--server-name SERVER]"
55 " [-c|--client-version CLIENT]";
57 #define RNET_ADDRESS "receitanet.receita.fazenda.gov.br"
59 /* Description and definition of each option accepted by the program. */
61 static const struct argp_option rnetclient_options_desc[] = {
62 { "declaration", 'd', "FILE", 0,
63 "The Income Tax Report file that will be sent.",
66 { "output-dir", 'o', "DIRECTORY", 0,
67 "The directory where you wish to save the receipt.",
70 { "server-name", 's', "SERVER", 0,
71 "The server to connect to. Default is " RNET_ADDRESS ".",
74 { "client-version", 'c', "CLIENT", 0,
75 "The client version to be used.",
81 struct rnetclient_args {
82 /* File representing the declaration. */
85 /* Output directory to save the receipt. */
88 /* Output filename. */
89 char output_file[PATH_MAX];
91 /* Server to connect to. */
94 /* Client version to use. */
98 /* Parser for command line arguments. */
100 static error_t rnetclient_parse_opt(int key, char *arg, struct argp_state *state)
102 struct rnetclient_args *a = state->input;
105 /* The user has explicitly provided a filename through
115 a->server_name = arg;
119 a->client_version = arg;
123 /* The user has possibly provided a filename without
124 using any switches (e.g., by running './rnetclient
130 /* We have reached the end of the argument parsing.
131 Let's check if the user has provided a filename. */
132 if (a->input_file == NULL)
134 "You need to provide the Income Tax Declaration "
141 /* Control struct used by argp. */
143 static struct argp rnetclient_argp = {
144 rnetclient_options_desc,
145 rnetclient_parse_opt,
151 static size_t chars2len (unsigned char buf[2]) {
152 return (buf[0] << 8 | buf[1]);
155 static void * get_creds(char *certfile)
157 static gnutls_certificate_credentials_t cred;
158 gnutls_certificate_allocate_credentials(&cred);
159 gnutls_certificate_set_x509_trust_file(cred, certfile,
160 GNUTLS_X509_FMT_PEM);
164 static void session_new(gnutls_session_t *session)
167 cred = get_creds("cert.pem");
168 gnutls_init(session, GNUTLS_CLIENT);
169 gnutls_set_default_priority(*session);
170 gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, cred);
173 static int deflateRecord(char *buffer, size_t len, char **out, size_t *olen, int header)
177 zstrm.zalloc = Z_NULL;
178 zstrm.zfree = Z_NULL;
179 zstrm.opaque = Z_NULL;
180 if ((r = deflateInit(&zstrm, Z_DEFAULT_COMPRESSION)) != Z_OK)
182 *out = malloc(len * 2 + 36);
187 zstrm.next_in = (z_const Bytef *) buffer;
188 zstrm.avail_in = len;
189 zstrm.next_out = (Bytef *) *out + 6;
190 zstrm.avail_out = len * 2 + 30;
191 while ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
192 zstrm.avail_out > 0);
193 if ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
198 *olen = zstrm.total_out + 6;
200 (*out)[1] = (zstrm.total_out >> 8);
201 (*out)[2] = (zstrm.total_out & 0xff);
202 (*out)[3] = (len >> 8);
203 (*out)[4] = (len & 0xff);
204 (*out)[5] = header ? 0x01 : 0x0;
209 static int inflateRecord(char *buffer, size_t len, char **out, size_t *olen)
213 zstrm.zalloc = Z_NULL;
214 zstrm.zfree = Z_NULL;
215 zstrm.opaque = Z_NULL;
216 if ((r = inflateInit(&zstrm)) != Z_OK)
218 *olen = chars2len((unsigned char *) buffer+3);
219 *out = malloc(*olen);
224 zstrm.next_in = (z_const Bytef *) buffer + 6;
225 zstrm.avail_in = len - 6;
226 zstrm.next_out = (Bytef *) *out;
227 zstrm.avail_out = *olen;
228 while ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
229 zstrm.avail_out > 0);
230 if ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
239 static int connect_rnet(int *c, char *server_name)
241 struct addrinfo *addresses;
242 struct addrinfo *addr;
243 struct addrinfo hint;
246 memset(&hint, 0, sizeof(hint));
247 hint.ai_family = AF_UNSPEC;
248 hint.ai_socktype = SOCK_STREAM;
249 hint.ai_protocol = IPPROTO_TCP;
250 hint.ai_flags = AI_ADDRCONFIG;
251 r = getaddrinfo(server_name, "3456", &hint, &addresses);
255 for (addr = addresses; addr != NULL; addr = addr->ai_next) {
256 fd = socket(addr->ai_family, addr->ai_socktype,
259 if (!(r = connect(fd, addr->ai_addr,
265 freeaddrinfo(addresses);
272 static int handshake(int c)
277 r = write(c, buffer, 1);
280 r = write(c, "00000000000000", 14);
283 r = read(c, buffer, 1);
284 if (r != 1 && buffer[0] != 'E')
286 r = read(c, buffer, 14);
292 static int rnet_send(gnutls_session_t session, char *buffer, size_t len, int header)
295 /* Large files have to be uploaded as multiple
296 separately-deflated chunks, because the compressed and
297 uncompressed lengths in each record are encoded in unsigned
298 16-bit integers each.
300 The header can't be split into multiple chunks, and it
301 should never have to, since it won't ever get even close to
304 The uploaded file may be larger: to upload such large
305 files, it suffices to send multiple records till the entire
306 file is transferred, without waiting for a response. Since
307 we've already informed the server of the file size in the
308 header, it knows exactly how much data to expect before
309 sending a response. It will only send an error message
310 before that if it times us out.
312 Odds are that any reasonably large size will do, but it
313 can't be too close to 64KiB, otherwise there won't be room
314 for the compressed length should it not compress well,
315 which should never happen for capital-ASCII-only
316 declaration files, but who knows?
318 This chunk size worked at the first try, uploading a
319 ~100KiB file, so let's stick with it. */
320 const unsigned int maxc = 64472;
321 if (header && len > maxc)
327 size_t clen = len < maxc ? len : maxc;
328 r = deflateRecord(buffer, clen, &out, &olen, header);
330 size_t n = gnutls_record_send(session, out, olen);
341 static int rnet_recv(gnutls_session_t session, struct rnet_message **message)
347 rnet_message_expand(message, 6);
348 buffer = (*message)->buffer;
349 gnutls_record_recv(session, buffer, 6);
350 if (buffer[0] == 0x01) {
351 len = chars2len((unsigned char *) buffer+1);
352 rnet_message_expand(message, len);
353 buffer = (*message)->buffer + 6;
354 gnutls_record_recv(session, buffer, len);
355 inflateRecord(buffer - 6, len + 6, &out, &olen);
356 rnet_message_del(*message);
358 rnet_message_expand(message, olen);
359 memcpy((*message)->buffer, out, olen);
360 (*message)->len = olen;
363 len = chars2len((unsigned char *) buffer+1);
364 rnet_message_expand(message, len - 1);
365 buffer = (*message)->buffer + 6;
366 gnutls_record_recv(session, buffer, len - 1);
367 (*message)->len = len + 4;
368 rnet_message_strip(*message, 4);
373 static int open_rec_file(char *cpf, struct rnetclient_args *args)
378 path = args->output_dir;
380 /* Now it's time to decide which filename to write. We use
381 the declaration's filename as a base layout, because the
382 proprietary version of the IRPF program only recognizes
383 receipts if they have the same name as the declaration
384 files (disconsidering the extensions). For example, if the
385 declaration file is named "123.DEC", the receipt should be
386 named "123.REC". Therefore, if the declaration file has
387 the ".DEC" extension, we strip it out and add the ".REC".
388 Otherwise, we use the default template, which is to save
389 the receipt with the name "$CPF.REC". */
390 tmp = strstr(args->input_file, ".DEC");
391 if (tmp != NULL && tmp[sizeof(".DEC") - 1] == '\0') {
393 /* We found the ".REC" extension. */
394 p = strdup(basename(args->input_file));
395 /* Replacing the ".DEC" by ".REC". Fortunately, we
396 just have to change one letter. */
397 tmp = strstr(p, ".DEC");
399 snprintf(args->output_file, PATH_MAX, "%s/%s", path, p);
402 /* The declaration filename does not follow the
403 convention, so we will not use it as a template.
404 We just generate a filename using "$CPF.REC". */
405 snprintf(args->output_file, PATH_MAX, "%s/%s.REC", path, cpf);
407 /* Now, open the file and write. */
408 fd = open(args->output_file, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
410 fprintf(stderr, "Could not create receipt file \"%s\": %s\n",
411 args->output_file, strerror(errno));
416 static int save_rec_file(int fd, char *buffer, int len, const struct rnetclient_args *args)
421 r = write(fd, buffer, len);
422 } while (r < 0 && errno == EAGAIN);
424 fprintf(stderr, "Could not write to receipt file: %s",
427 fprintf(stderr, "Wrote the receipt file to %s.\n",
433 static void handle_response_text_and_file(int fd, struct rnet_message *message, const struct rnetclient_args *args)
437 if (!rnet_message_parse(message, "texto", &value, &vlen))
438 fprintf(stderr, "%.*s\n", vlen, value);
439 if (!rnet_message_parse(message, "arquivo", &value, &vlen))
440 save_rec_file(fd, value, vlen, args);
443 static void handle_response_already_found(int fd, struct rnet_message *message, const struct rnetclient_args *args)
445 handle_response_text_and_file(fd, message, args);
448 static void handle_response_error(struct rnet_message *message)
452 if (!rnet_message_parse(message, "texto", &value, &vlen))
453 fprintf(stderr, "%.*s\n", vlen, value);
454 fprintf(stderr, "Error transmiting DEC file.\n");
457 int main(int argc, char **argv)
461 struct rnet_decfile *decfile;
462 struct rnet_message *message = NULL;
463 struct rnetclient_args rnet_args;
464 gnutls_session_t session;
471 /* Parsing the command line arguments. The argp_parse
472 function calls exit() if there is some error during the
473 parsing process (e.g., the user has provided an unknown
474 flag or the parsing function has called argp_error).
475 However, if our internal parsing function returns something
476 different than zero, then argp_parse returns this value to
477 us. This is a bug, and should not happen in the current
479 memset(&rnet_args, 0, sizeof (rnet_args));
480 rnet_args.server_name = RNET_ADDRESS;
481 rnet_args.client_version = RNET_DEFAULT_VERSION;
482 err = argp_parse (&rnetclient_argp, argc, argv, 0, NULL, &rnet_args);
484 fprintf(stderr, "internal error while parsing command line arguments.");
486 /* If the user provided the output directory where she wishes
487 to save the receipt, then we use it. Otherwise, we save
488 the file in the current working directory (CWD). */
489 if (rnet_args.output_dir == NULL) {
490 rnet_args.output_dir = getcwd(cwd, PATH_MAX);
493 if (stat(rnet_args.output_dir, &st) < 0) {
494 fprintf(stderr, "Could not stat directory \"%s\": %s\n",
495 rnet_args.output_dir, strerror(errno));
498 if (!S_ISDIR(st.st_mode)) {
499 fprintf(stderr, "Error: \"%s\" is a not a directory.\n",
500 rnet_args.output_dir);
505 decfile = rnet_decfile_open(rnet_args.input_file);
507 fprintf(stderr, "could not parse file \"%s\": %s\n", rnet_args.input_file, strerror(errno));
511 cpf = rnet_decfile_get_header_field(decfile, "cpf");
513 outfd = open_rec_file(cpf, &rnet_args);
519 gnutls_global_init();
521 session_new(&session);
522 r = connect_rnet(&c, rnet_args.server_name);
524 fprintf(stderr, "error connecting to server: %s\n",
525 r == EAI_SYSTEM ? strerror(errno) : gai_strerror(r));
529 gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(intptr_t) c);
535 if ((r = gnutls_handshake(session)) < 0) {
536 fprintf(stderr, "error in handshake: %s\n",
542 r = rnet_encode(decfile, &message, rnet_args.client_version);
544 fprintf(stderr, "error encoding message, file not supported?\n");
549 rnet_send(session, message->buffer, message->len, 1);
550 rnet_message_del(message);
553 r = rnet_recv(session, &message);
554 if (r || !message || message->len == 0) {
555 fprintf(stderr, "error when receiving response\n");
559 switch (message->buffer[0]) {
560 case 1: /* go ahead */
561 handle_response_text_and_file(outfd, message, &rnet_args);
564 handle_response_error(message);
568 handle_response_already_found(outfd, message, &rnet_args);
573 handle_response_text_and_file(outfd, message, &rnet_args);
577 rnet_message_del(message);
582 message = rnet_decfile_get_file(decfile);
583 rnet_send(session, message->buffer, message->len, 0);
586 r = rnet_recv(session, &message);
587 if (r || !message || message->len == 0) {
588 fprintf(stderr, "error when receiving response\n");
592 switch (message->buffer[0]) {
594 handle_response_error(message);
600 handle_response_text_and_file(outfd, message, &rnet_args);
605 gnutls_bye(session, GNUTLS_SHUT_RDWR);
609 gnutls_global_deinit();
612 rnet_decfile_close(decfile);