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>
31 #include "rnet_message.h"
32 #include "rnet_encode.h"
34 static size_t chars2len (unsigned char buf[2]) {
35 return (buf[0] << 8 | buf[1]);
38 static void * get_creds(char *certfile)
40 static gnutls_certificate_credentials_t cred;
41 gnutls_certificate_allocate_credentials(&cred);
42 gnutls_certificate_set_x509_trust_file(cred, certfile,
47 static void session_new(gnutls_session_t *session)
50 cred = get_creds("cert.pem");
51 gnutls_init(session, GNUTLS_CLIENT);
52 gnutls_set_default_priority(*session);
53 gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, cred);
56 static int deflateRecord(char *buffer, size_t len, char **out, size_t *olen, int header)
60 zstrm.zalloc = Z_NULL;
62 zstrm.opaque = Z_NULL;
63 if ((r = deflateInit(&zstrm, Z_DEFAULT_COMPRESSION)) != Z_OK)
65 *out = malloc(len * 2 + 36);
70 zstrm.next_in = buffer;
72 zstrm.next_out = *out + 6;
73 zstrm.avail_out = len * 2 + 30;
74 while ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
76 if ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
81 *olen = zstrm.total_out + 6;
83 (*out)[1] = (zstrm.total_out >> 8);
84 (*out)[2] = (zstrm.total_out & 0xff);
85 (*out)[3] = (len >> 8);
86 (*out)[4] = (len & 0xff);
87 (*out)[5] = header ? 0x01 : 0x0;
92 static int inflateRecord(char *buffer, size_t len, char **out, size_t *olen)
96 zstrm.zalloc = Z_NULL;
98 zstrm.opaque = Z_NULL;
99 if ((r = inflateInit(&zstrm)) != Z_OK)
101 *olen = chars2len(buffer+3);
102 *out = malloc(*olen);
107 zstrm.next_in = buffer + 6;
108 zstrm.avail_in = len - 6;
109 zstrm.next_out = *out;
110 zstrm.avail_out = *olen;
111 while ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
112 zstrm.avail_out > 0);
113 if ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
122 #define RNET_ADDRESS "receitanet.receita.fazenda.gov.br"
124 static int connect_rnet(int *c)
126 struct addrinfo *addresses;
127 struct addrinfo *addr;
128 struct addrinfo hint;
129 struct sockaddr_in saddr;
133 memset(&hint, 0, sizeof(hint));
134 hint.ai_family = AF_UNSPEC;
135 hint.ai_socktype = SOCK_STREAM;
136 hint.ai_protocol = IPPROTO_TCP;
137 hint.ai_flags = AI_ADDRCONFIG;
138 r = getaddrinfo(RNET_ADDRESS, "3456", &hint, &addresses);
142 for (addr = addresses; addr != NULL; addr = addr->ai_next) {
143 fd = socket(addr->ai_family, addr->ai_socktype,
146 if (!(r = connect(fd, addr->ai_addr,
152 freeaddrinfo(addresses);
159 static int handshake(int c)
165 write(c, "00000000000000", 14);
166 r = read(c, buffer, 1);
167 if (r != 1 && buffer[0] != 'E')
169 r = read(c, buffer, 14);
175 static void usage(void)
177 fprintf(stderr, "rnetclient [filename]\n");
181 static int rnet_send(gnutls_session_t session, char *buffer, size_t len, int header)
184 /* Large files have to be uploaded as multiple
185 separately-deflated chunks, because the compressed and
186 uncompressed lengths in each record are encoded in unsigned
187 16-bit integers each.
189 The header can't be split into multiple chunks, and it
190 should never have to, since it won't ever get even close to
193 The uploaded file may be larger: to upload such large
194 files, it suffices to send multiple records till the entire
195 file is transferred, without waiting for a response. Since
196 we've alread informed the server of the file size in the
197 header, it knows exactly how much data to expect before
198 sending a response. It will only send an error message
199 before that if it times us out.
201 Odds are that any reasonably large size will do, but it
202 can't be too close to 64KiB, otherwise there won't be room
203 for the compressed length should it not compress well,
204 which should never happen for capital-ASCII-only
205 declaration files, but who knows?
207 This chunk size worked at the first try, uploading a
208 ~100KiB file, so let's stick with it. */
209 const int maxc = 64472;
210 if (header && len > maxc)
216 size_t clen = len < maxc ? len : maxc;
217 r = deflateRecord(buffer, clen, &out, &olen, header);
219 size_t n = gnutls_record_send(session, out, olen);
230 static int rnet_recv(gnutls_session_t session, struct rnet_message **message)
237 rnet_message_expand(message, 6);
238 buffer = (*message)->buffer;
239 r = gnutls_record_recv(session, buffer, 6);
240 if (buffer[0] == 0x01) {
241 len = chars2len(buffer+1);
242 rnet_message_expand(message, len);
243 buffer = (*message)->buffer + 6;
244 r = gnutls_record_recv(session, buffer, len);
245 inflateRecord(buffer - 6, len + 6, &out, &olen);
246 rnet_message_del(*message);
248 rnet_message_expand(message, olen);
249 memcpy((*message)->buffer, out, olen);
250 (*message)->len = olen;
253 len = chars2len(buffer+1);
254 rnet_message_expand(message, len - 1);
255 buffer = (*message)->buffer + 6;
256 r = gnutls_record_recv(session, buffer, len - 1);
257 (*message)->len = len + 4;
258 rnet_message_strip(*message, 4);
263 static void save_rec_file(char *cpf, char *buffer, int len)
271 home = getenv("HOME");
273 tmpdir = getenv("TMPDIR");
278 fnlen = strlen(home) + strlen(cpf) + 13;
279 filename = malloc(fnlen);
280 snprintf(filename, fnlen, "%s/%s.REC.XXXXXX", home, cpf);
282 fd = mkstemp(filename);
284 fprintf(stderr, "Could not create receipt file: %s\n",
288 r = write(fd, buffer, len);
290 fprintf(stderr, "Could not write to receipt file%s%s\n",
292 r < 0 ? strerror(errno) : "");
295 fprintf(stderr, "Wrote the receipt to %s.\n", filename);
302 static void handle_response_text_and_file(char *cpf, struct rnet_message *message)
306 if (!rnet_message_parse(message, "texto", &value, &vlen))
307 fprintf(stderr, "%.*s\n", vlen, value);
308 if (!rnet_message_parse(message, "arquivo", &value, &vlen))
309 save_rec_file(cpf, value, vlen);
312 static void handle_response_already_found(char *cpf, struct rnet_message *message)
314 handle_response_text_and_file(cpf, message);
317 static void handle_response_error(struct rnet_message *message)
321 if (!rnet_message_parse(message, "texto", &value, &vlen))
322 fprintf(stderr, "%.*s\n", vlen, value);
323 fprintf(stderr, "Error transmiting DEC file.\n");
326 int main(int argc, char **argv)
330 struct rnet_decfile *decfile;
331 struct rnet_message *message = NULL;
332 gnutls_session_t session;
340 decfile = rnet_decfile_open(argv[1]);
342 fprintf(stderr, "could not parse %s: %s\n", argv[1], strerror(errno));
346 cpf = rnet_decfile_get_header_field(decfile, "cpf");
348 gnutls_global_init();
350 session_new(&session);
351 r = connect_rnet(&c);
353 fprintf(stderr, "error connecting to server: %s\n",
354 r == EAI_SYSTEM ? strerror(errno) : gai_strerror(r));
357 gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(intptr_t) c);
362 if ((r = gnutls_handshake(session)) < 0)
363 fprintf(stderr, "error in handshake: %s\n",
366 rnet_encode(decfile, &message);
367 rnet_send(session, message->buffer, message->len, 1);
368 rnet_message_del(message);
371 r = rnet_recv(session, &message);
372 if (r || !message || message->len == 0) {
373 fprintf(stderr, "error when receiving response\n");
376 switch (message->buffer[0]) {
377 case 1: /* go ahead */
378 handle_response_text_and_file(cpf, message);
381 handle_response_error(message);
385 handle_response_already_found(cpf, message);
390 handle_response_text_and_file(cpf, message);
394 rnet_message_del(message);
399 message = rnet_decfile_get_file(decfile);
400 rnet_send(session, message->buffer, message->len, 0);
403 r = rnet_recv(session, &message);
404 if (r || !message || message->len == 0) {
405 fprintf(stderr, "error when receiving response\n");
408 switch (message->buffer[0]) {
410 handle_response_error(message);
416 handle_response_text_and_file(cpf, message);
421 gnutls_bye(session, GNUTLS_SHUT_RDWR);
423 rnet_decfile_close(decfile);
424 gnutls_global_deinit();