2 * Copyright (C) 2012-2013 Thadeu Lima de Souza Cascardo <cascardo@minaslivre.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
28 #include <gnutls/gnutls.h>
33 #include "rnet_message.h"
34 #include "rnet_encode.h"
36 /* Program version and bug report address. */
38 const char *argp_program_version = PACKAGE_VERSION;
39 const char *argp_program_bug_address = PACKAGE_BUGREPORT;
41 /* Documentation strings. */
43 static const char rnetclient_doc[] =
44 "Send the Brazilian Income Tax Report to the Brazilian "
46 static const char rnetclient_args_doc[] =
47 "[-d|--declaration] FILE";
49 /* Description and definition of each option accepted by the program. */
51 static const struct argp_option rnetclient_options_desc[] = {
52 { "declaration", 'd', "FILE", OPTION_ARG_OPTIONAL,
53 "The Income Tax Report file that will be sent.",
59 struct rnetclient_args {
60 /* File representing the declaration. */
64 /* Parser for command line arguments. */
66 static error_t rnetclient_parse_opt(int key, char *arg, struct argp_state *state)
68 struct rnetclient_args *a = state->input;
71 /* The user has explicitly provided a filename through
77 /* The user has possibly provided a filename without
78 using any switches (e.g., by running './rnetclient
84 /* We have reached the end of the argument parsing.
85 Let's check if the user has provided a filename. */
86 if (a->input_file == NULL)
88 "You need to provide the Income Tax Declaration "
95 /* Control struct used by argp. */
97 static struct argp rnetclient_argp = {
98 rnetclient_options_desc,
105 static size_t chars2len (unsigned char buf[2]) {
106 return (buf[0] << 8 | buf[1]);
109 static void * get_creds(char *certfile)
111 static gnutls_certificate_credentials_t cred;
112 gnutls_certificate_allocate_credentials(&cred);
113 gnutls_certificate_set_x509_trust_file(cred, certfile,
114 GNUTLS_X509_FMT_PEM);
118 static void session_new(gnutls_session_t *session)
121 cred = get_creds("cert.pem");
122 gnutls_init(session, GNUTLS_CLIENT);
123 gnutls_set_default_priority(*session);
124 gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, cred);
127 static int deflateRecord(char *buffer, size_t len, char **out, size_t *olen, int header)
131 zstrm.zalloc = Z_NULL;
132 zstrm.zfree = Z_NULL;
133 zstrm.opaque = Z_NULL;
134 if ((r = deflateInit(&zstrm, Z_DEFAULT_COMPRESSION)) != Z_OK)
136 *out = malloc(len * 2 + 36);
141 zstrm.next_in = buffer;
142 zstrm.avail_in = len;
143 zstrm.next_out = *out + 6;
144 zstrm.avail_out = len * 2 + 30;
145 while ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
146 zstrm.avail_out > 0);
147 if ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
152 *olen = zstrm.total_out + 6;
154 (*out)[1] = (zstrm.total_out >> 8);
155 (*out)[2] = (zstrm.total_out & 0xff);
156 (*out)[3] = (len >> 8);
157 (*out)[4] = (len & 0xff);
158 (*out)[5] = header ? 0x01 : 0x0;
163 static int inflateRecord(char *buffer, size_t len, char **out, size_t *olen)
167 zstrm.zalloc = Z_NULL;
168 zstrm.zfree = Z_NULL;
169 zstrm.opaque = Z_NULL;
170 if ((r = inflateInit(&zstrm)) != Z_OK)
172 *olen = chars2len(buffer+3);
173 *out = malloc(*olen);
178 zstrm.next_in = buffer + 6;
179 zstrm.avail_in = len - 6;
180 zstrm.next_out = *out;
181 zstrm.avail_out = *olen;
182 while ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
183 zstrm.avail_out > 0);
184 if ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
193 #define RNET_ADDRESS "receitanet.receita.fazenda.gov.br"
195 static int connect_rnet(int *c)
197 struct addrinfo *addresses;
198 struct addrinfo *addr;
199 struct addrinfo hint;
200 struct sockaddr_in saddr;
204 memset(&hint, 0, sizeof(hint));
205 hint.ai_family = AF_UNSPEC;
206 hint.ai_socktype = SOCK_STREAM;
207 hint.ai_protocol = IPPROTO_TCP;
208 hint.ai_flags = AI_ADDRCONFIG;
209 r = getaddrinfo(RNET_ADDRESS, "3456", &hint, &addresses);
213 for (addr = addresses; addr != NULL; addr = addr->ai_next) {
214 fd = socket(addr->ai_family, addr->ai_socktype,
217 if (!(r = connect(fd, addr->ai_addr,
223 freeaddrinfo(addresses);
230 static int handshake(int c)
235 r = write(c, buffer, 1);
238 r = write(c, "00000000000000", 14);
241 r = read(c, buffer, 1);
242 if (r != 1 && buffer[0] != 'E')
244 r = read(c, buffer, 14);
250 static int rnet_send(gnutls_session_t session, char *buffer, size_t len, int header)
253 /* Large files have to be uploaded as multiple
254 separately-deflated chunks, because the compressed and
255 uncompressed lengths in each record are encoded in unsigned
256 16-bit integers each.
258 The header can't be split into multiple chunks, and it
259 should never have to, since it won't ever get even close to
262 The uploaded file may be larger: to upload such large
263 files, it suffices to send multiple records till the entire
264 file is transferred, without waiting for a response. Since
265 we've alread informed the server of the file size in the
266 header, it knows exactly how much data to expect before
267 sending a response. It will only send an error message
268 before that if it times us out.
270 Odds are that any reasonably large size will do, but it
271 can't be too close to 64KiB, otherwise there won't be room
272 for the compressed length should it not compress well,
273 which should never happen for capital-ASCII-only
274 declaration files, but who knows?
276 This chunk size worked at the first try, uploading a
277 ~100KiB file, so let's stick with it. */
278 const int maxc = 64472;
279 if (header && len > maxc)
285 size_t clen = len < maxc ? len : maxc;
286 r = deflateRecord(buffer, clen, &out, &olen, header);
288 size_t n = gnutls_record_send(session, out, olen);
299 static int rnet_recv(gnutls_session_t session, struct rnet_message **message)
306 rnet_message_expand(message, 6);
307 buffer = (*message)->buffer;
308 r = gnutls_record_recv(session, buffer, 6);
309 if (buffer[0] == 0x01) {
310 len = chars2len(buffer+1);
311 rnet_message_expand(message, len);
312 buffer = (*message)->buffer + 6;
313 r = gnutls_record_recv(session, buffer, len);
314 inflateRecord(buffer - 6, len + 6, &out, &olen);
315 rnet_message_del(*message);
317 rnet_message_expand(message, olen);
318 memcpy((*message)->buffer, out, olen);
319 (*message)->len = olen;
322 len = chars2len(buffer+1);
323 rnet_message_expand(message, len - 1);
324 buffer = (*message)->buffer + 6;
325 r = gnutls_record_recv(session, buffer, len - 1);
326 (*message)->len = len + 4;
327 rnet_message_strip(*message, 4);
332 static void save_rec_file(char *cpf, char *buffer, int len)
340 home = getenv("HOME");
342 tmpdir = getenv("TMPDIR");
347 fnlen = strlen(home) + strlen(cpf) + 13;
348 filename = malloc(fnlen);
349 snprintf(filename, fnlen, "%s/%s.REC.XXXXXX", home, cpf);
351 fd = mkstemp(filename);
353 fprintf(stderr, "Could not create receipt file: %s\n",
357 r = write(fd, buffer, len);
359 fprintf(stderr, "Could not write to receipt file%s%s\n",
361 r < 0 ? strerror(errno) : "");
364 fprintf(stderr, "Wrote the receipt to %s.\n", filename);
371 static void handle_response_text_and_file(char *cpf, struct rnet_message *message)
375 if (!rnet_message_parse(message, "texto", &value, &vlen))
376 fprintf(stderr, "%.*s\n", vlen, value);
377 if (!rnet_message_parse(message, "arquivo", &value, &vlen))
378 save_rec_file(cpf, value, vlen);
381 static void handle_response_already_found(char *cpf, struct rnet_message *message)
383 handle_response_text_and_file(cpf, message);
386 static void handle_response_error(struct rnet_message *message)
390 if (!rnet_message_parse(message, "texto", &value, &vlen))
391 fprintf(stderr, "%.*s\n", vlen, value);
392 fprintf(stderr, "Error transmiting DEC file.\n");
395 int main(int argc, char **argv)
399 struct rnet_decfile *decfile;
400 struct rnet_message *message = NULL;
401 struct rnetclient_args rnet_args;
402 gnutls_session_t session;
407 /* Parsing the command line arguments. The argp_parse
408 function calls exit() if there is some error during the
409 parsing process (e.g., the user has provided an unknown
410 flag or the parsing function has called argp_error).
411 However, if our internal parsing function returns something
412 different than zero, then argp_parse returns this value to
413 us. This is a bug, and should not happen in the current
415 memset(&rnet_args, 0, sizeof (rnet_args));
416 err = argp_parse (&rnetclient_argp, argc, argv, 0, NULL, &rnet_args);
418 fprintf(stderr, "internal error while parsing command line arguments.");
420 decfile = rnet_decfile_open(rnet_args.input_file);
422 fprintf(stderr, "could not parse file \"%s\": %s\n", rnet_args.input_file, strerror(errno));
426 cpf = rnet_decfile_get_header_field(decfile, "cpf");
428 gnutls_global_init();
430 session_new(&session);
431 r = connect_rnet(&c);
433 fprintf(stderr, "error connecting to server: %s\n",
434 r == EAI_SYSTEM ? strerror(errno) : gai_strerror(r));
437 gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(intptr_t) c);
442 if ((r = gnutls_handshake(session)) < 0)
443 fprintf(stderr, "error in handshake: %s\n",
446 rnet_encode(decfile, &message);
447 rnet_send(session, message->buffer, message->len, 1);
448 rnet_message_del(message);
451 r = rnet_recv(session, &message);
452 if (r || !message || message->len == 0) {
453 fprintf(stderr, "error when receiving response\n");
456 switch (message->buffer[0]) {
457 case 1: /* go ahead */
458 handle_response_text_and_file(cpf, message);
461 handle_response_error(message);
465 handle_response_already_found(cpf, message);
470 handle_response_text_and_file(cpf, message);
474 rnet_message_del(message);
479 message = rnet_decfile_get_file(decfile);
480 rnet_send(session, message->buffer, message->len, 0);
483 r = rnet_recv(session, &message);
484 if (r || !message || message->len == 0) {
485 fprintf(stderr, "error when receiving response\n");
488 switch (message->buffer[0]) {
490 handle_response_error(message);
496 handle_response_text_and_file(cpf, message);
501 gnutls_bye(session, GNUTLS_SHUT_RDWR);
503 rnet_decfile_close(decfile);
504 gnutls_global_deinit();