Watt-32 tcp/ip  2.2 dev-rel.10
dynip.c
Go to the documentation of this file.
1 
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <limits.h>
14 #include <ctype.h>
15 #include <netdb.h>
16 
17 #include "wattcp.h"
18 #include "strings.h"
19 #include "language.h"
20 #include "misc.h"
21 #include "run.h"
22 #include "pcconfig.h"
23 #include "pcbuf.h"
24 #include "pctcp.h"
25 #include "pcdns.h"
26 #include "netaddr.h"
27 #include "dynip.h"
28 
29 #if defined(USE_DYNIP_CLI)
30 
31 #if defined(SNPRINTF)
32  #define BUF(buf) buf, sizeof(buf)
33  #define _SNPRINTF SNPRINTF
34 
35 #else
36  #define BUF(buf) buf
37  #define _SNPRINTF sprintf
38 #endif
39 
40 #if defined(USE_DEBUG)
41  #define TRACE(level,arg) \
42  do { \
43  if (trace_level >= level && dynip_enable) { \
44  (*_printf) arg; \
45  fflush (stdout); \
46  } \
47  } while (0)
48 #else
49  #define TRACE(level,arg) ((void)0)
50 #endif
51 
52 struct URL {
53  char *host;
54  char *get_req;
55  WORD port;
56  BOOL on_heap;
57  };
58 
59 static char dyndns_user [MAX_VALUELEN+1] = "";
60 static char dyndns_passwd [MAX_VALUELEN+1] = "";
61 static char dyn_myhostname [MAX_VALUELEN+1] = "";
62 static char dyn_myip [20] = "";
63 static BOOL dynip_enable = FALSE;
64 static int dyndns_refresh = 3600;
65 static int trace_level = 0;
66 static char config_file [MAX_PATHLEN+1] = "$(TEMP)\\W32DYNIP.TMP";
67 
68 static struct URL dyndns = { (char*) "members.dyndns.com",
69  (char*) "/nic/update?system=dyndns&hostname=%s",
70  80, FALSE
71  };
72 static struct URL chkip = { NULL, NULL, 0, FALSE };
73 
74 static char resp_buf [2048];
75 static void (W32_CALL *prev_hook) (const char*, const char*);
76 
77 static int get_url (const char *host, int port, const char *path,
78  const char *user_pass);
79 
80 static const char *parse_myip_address (const char *buf);
81 static int parse_dyndns_response (const char *buf);
82 static int chkip_method (const char *value);
83 static int dyndns_params (const char *value);
84 static void url_free (struct URL *url);
85 static BOOL url_parse (struct URL *res, const char *url);
86 
87 
93 static void W32_CALL dynip_config (const char *name, const char *value)
94 {
95  static const struct config_table dynip_cfg[] = {
96  { "ENABLE", ARG_ATOI, (void*) &dynip_enable },
97  { "MY_IPADDRESS", ARG_FUNC, (void*) chkip_method },
98  { "MY_HOSTNAME", ARG_STRCPY, (void*) &dyn_myhostname },
99  { "UPDATE", ARG_FUNC, (void*) dyndns_params },
100  { "USER", ARG_STRCPY, (void*) &dyndns_user },
101  { "PASSWD", ARG_STRCPY, (void*) &dyndns_passwd },
102  { "REFRESH", ARG_ATOI, (void*) &dyndns_refresh },
103  { "TRACE", ARG_ATOI, (void*) &trace_level },
104  { "CONFIG", ARG_STRCPY, (void*) &config_file }, /* \todo */
105  { NULL, 0, NULL }
106  };
107 
108  if (!parse_config_table(&dynip_cfg[0], "DYNIP.", name, value) && prev_hook)
109  (*prev_hook) (name, value);
110 }
111 
115 static void dynip_exit (void)
116 {
117  if (_watt_fatal_error)
118  return;
119 
120  url_free (&chkip);
121  url_free (&dyndns);
122 }
123 
124 void dynip_init (void)
125 {
126  prev_hook = usr_init;
127  usr_init = dynip_config;
128  RUNDOWN_ADD (dynip_exit, 262);
129 }
130 
131 /*
132  * Do this at most once each hour:
133  *
134  * 1. Get our WAN ip (<wan-ip> below) by fetching:
135  * http://checkip.dyndns.com/ or http://ipdetect.dnspark.com/
136  *
137  * 2. Send HTTP request to members.dyndns.com:
138  * GET /nic/update?system=dyndns&hostname=<host-name>&myip=<wan-ip> HTTP/1.1
139  * Host: members.dyndns.com
140  * Authorization: Basic <user-name>:<password> base64 encoded
141  * Connection: close
142  *
143  * 3. Get result: "nohost" means <host-name> isn't registered in DynDNS.
144  * "noauth" means wrong <user-name> or <passord>.
145  * "notfqdn" means invalid <host-name> etc.
146  * "nochg <wan-ip>" means no update needed.
147  * "good <wan-ip>" means the update was acccepted.
148  *
149  * \todo: this should be rewritten as a non-blocking Watt-32 daemon.
150  * Use daemon_add (dynip_exec).
151  */
152 int dynip_exec (void)
153 {
154  char auth_buf [512] = "";
155  char request [512], *p;
156  int num_arg, rc = 0;
157 
158  if (!dynip_enable)
159  return (0);
160 
161  if (!dyn_myip[0] && chkip.host) /* Not already entered as dotted quad */
162  {
163  const char *myip;
164 
165  rc = get_url (chkip.host, chkip.port, chkip.get_req, NULL);
166  if (rc <= 0)
167  return (0);
168 
169  myip = parse_myip_address (resp_buf);
170 
171  TRACE (2, ("Got %d bytes HTML, Found myip: `%s'\r\n", rc, myip));
172  if (!myip)
173  return (0);
174  _strlcpy (dyn_myip, myip, sizeof(dyn_myip));
175  }
176 
177  if (!dyn_myhostname[0] || !dyndns.get_req)
178  return (0);
179 
180  for (num_arg = 0, p = strstr(dyndns.get_req,"%s"); p;
181  p = strstr(p+1,"%s"))
182  num_arg++;
183 
184  if (num_arg != 1 && num_arg != 2)
185  {
186  TRACE (1, ("Update URL (%s) must contain 1 or 2 \"%%s\" parameters\n",
187  dyndns.get_req));
188  return (0);
189  }
190 
191  if (dyndns_user[0] && dyndns_passwd[0])
192  _SNPRINTF (BUF(auth_buf), "%s:%s", dyndns_user, dyndns_passwd);
193  else if (dyndns_user[0])
194  _SNPRINTF (BUF(auth_buf), "%s", dyndns_user);
195 
196  _SNPRINTF (BUF(request), dyndns.get_req, dyn_myhostname, dyn_myip);
197 
198  if (!dyn_myip[0])
199  {
200  p = strrchr (request, '&'); /* chop of "&myip.." */
201  if (p)
202  *p = '\0';
203  }
204  rc = get_url (dyndns.host, dyndns.port, request,
205  *auth_buf ? auth_buf : NULL);
206  if (rc <= 0)
207  return (0);
208  return parse_dyndns_response (resp_buf);
209 }
210 
215 static const char *parse_myip_address (const char *orig)
216 {
217  const char *body, *end, *ret, *p;
218  char *buf;
219  static char res[20];
220 
221  buf = strdup (orig);
222  if (!buf)
223  {
224  TRACE (1, ("No memory\n"));
225  return (NULL);
226  }
227 
228  strlwr (buf);
229 
230  /* Accept "<body>" or "<body bgcolor..>" etc.
231  */
232  body = strstr (buf, "<body");
233  if (!body)
234  {
235  TRACE (1, ("No <body>\n"));
236  free (buf);
237  return (NULL);
238  }
239 
240  p = body + sizeof("<body")-1;
241  end = strstr (p, "</body>");
242  if (!end)
243  {
244  TRACE (1, ("No </body>\n"));
245  free (buf);
246  return (NULL);
247  }
248 
249  /* Find an IP-address between 'p' and 'end'
250  */
251  TRACE (2, ("p: `%s', len %d\n", p, (int)(end-p)));
252  ret = NULL;
253 
254  for ( ; *p && p < end; p++)
255  if (isdigit((int)*p) && aton(p))
256  {
257  ret = _strlcpy (res, p, min(end-p+1,SIZEOF(res)));
258  break;
259  }
260  free (buf);
261  return (ret);
262 }
263 
267 static int parse_dyndns_response (const char *buf)
268 {
269  TRACE (1, ("DynDNS resp: `%s'\n", buf));
270 
271 #if 0
272  if (!strncmp(buf,"nohost",6))
273  ;
274  if (!strncmp(buf,"noauth",6))
275  ;
276  if (!strncmp(buf,"notfqdn",7))
277  ;
278  if (!strncmp(buf,"nochg",5))
279  ;
280  if (!strncmp(buf,"good",4))
281  ;
282  if (!strncmp(buf,"!yours",6))
283  ;
284  if (!strncmp(buf,"abuse",5))
285  ;
286  if (!strncmp(buf,"numhost",7))
287  ;
288  if (!strncmp(buf,"dnserr",6))
289  ;
290 #endif
291  ARGSUSED (buf);
292  return (1);
293 }
294 
299 static BOOL url_parse (struct URL *res, const char *url)
300 {
301  char scheme [20+1];
302  char buf [256+1];
303  char *p;
304  const char *orig_url = url;
305  int num;
306 
307  res->port = 80; /* default values */
308  res->host = NULL;
309  res->get_req = NULL;
310  res->on_heap = FALSE;
311 
312  p = strstr (url, "://");
313  if (p)
314  {
315  _strlcpy (scheme, url, min(SIZEOF(scheme), p-url+1));
316  if (stricmp(scheme,"http"))
317  {
318  TRACE (1, ("Unsupported scheme `%s'\n", scheme));
319  return (FALSE);
320  }
321  url = p + 3;
322  }
323 
324  num = sscanf (url, "%256[^/]/%*s", buf);
325 
326  p = strrchr (url, '/'); /* workaround for djgpp 2.04 sscanf() bug */
327  if (p && !*(p+1))
328  num = 1;
329 
330  if (num >= 1)
331  {
332  p = strchr (buf, ':');
333  if (p)
334  {
335  res->port = atoi (p+1);
336  *p = '\0';
337  }
338  res->host = strdup (buf);
339  res->on_heap = TRUE;
340  p = strchr (url, '/');
341  res->get_req = p ? strdup (p) : strdup ("/");
342  TRACE (2, ("url_parse: host `%s', port %u, req `%s'\r\n",
343  res->host, res->port, res->get_req));
344  return (TRUE);
345  }
346  TRACE (1, ("Malformed URL `%s'\r\n", orig_url));
347  ARGSUSED (orig_url);
348  return (FALSE);
349 }
350 
354 static void url_free (struct URL *url)
355 {
356  if (url->on_heap)
357  {
358  DO_FREE (url->get_req);
359  DO_FREE (url->host);
360  }
361  url->on_heap = FALSE;
362 }
363 
368 static int chkip_method (const char *value)
369 {
370  if (isaddr(value))
371  _strlcpy (dyn_myip, value, sizeof(dyn_myip));
372  else
373  {
374  url_free (&chkip);
375  url_parse (&chkip, value);
376  }
377  return (1);
378 }
379 
384 static int dyndns_params (const char *value)
385 {
386  url_free (&dyndns);
387  url_parse (&dyndns, value);
388  return (1);
389 }
390 
391 /*
392  * Taken from:
393  *
394  * HTGET
395  *
396  * Get a document via HTTP
397  *
398  * This program was compiled under Borland C++ 3.1 in C mode for the small
399  * model. You will require the WATTCP libraries. A copy is included in the
400  * distribution. For the sources of WATTCP, ask archie where the archives
401  * are.
402  *
403  * Please send bug fixes, ports and enhancements to the author for
404  * incorporation in newer versions.
405  *
406  * Copyright (C) 1996 Ken Yap (ken@syd.dit.csiro.au)
407  *
408  * This program is free software; you can redistribute it and/or modify it
409  * under the terms of the Artistic License, a copy of which is included
410  * with this distribution.
411  *
412  * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
413  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
414  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
415  */
416 #define USER_AGENT "Watt-32/DynIP-client 0.5"
417 #define AUTHOR "Copyright (C) 1996 Ken Yap (ken@syd.dit.csiro.au)"
418 
419 #define HTTPVER "HTTP/1.0" /* HTTP version to use */
420 #define HTTPVER_10 "HTTP/1.0"
421 #define HTTPVER_11 "HTTP/1.1"
422 #define strn(s) s, sizeof(s)-1
423 
424 static int base64encode (const char *in, char *out, size_t out_len)
425 {
426  int c1, c2, c3;
427  static char basis_64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"\
428  "abcdefghijklmnopqrstuvwxyz0123456789+/";
429  while (*in)
430  {
431  c1 = *in++;
432  if (*in == '\0')
433  c2 = c3 = 0;
434  else
435  {
436  c2 = *in++;
437  if (*in == '\0')
438  c3 = 0;
439  else c3 = *in++;
440  }
441  out_len -= 4;
442  if (out_len <= 4)
443  return (-1);
444 
445  *out++ = basis_64 [c1 >> 2];
446  *out++ = basis_64 [((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)];
447  if (c2 == 0 && c3 == 0)
448  {
449  *out++ = '=';
450  *out++ = '=';
451  }
452  else if (c3 == 0)
453  {
454  *out++ = basis_64 [((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)];
455  *out++ = '=';
456  }
457  else
458  {
459  *out++ = basis_64 [((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)];
460  *out++ = basis_64 [c3 & 0x3F];
461  }
462  }
463  *out = '\0';
464  return (0);
465 }
466 
472 static BOOL get_header (void *sock, long *cont_len_ptr)
473 {
474  int len, response;
475  long content_length;
476  size_t i;
477  char buf [512];
478  char *s;
479 
480  *cont_len_ptr = 0L;
481 
482  len = sock_gets (sock, (BYTE*)buf, sizeof(buf));
483  if (len <= 0)
484  {
485  TRACE (1, ("EOF from server\n"));
486  return (FALSE);
487  }
488  TRACE (1, ("HTTP: `%.*s'\r\n", len, buf));
489 
490  if (strncmp(buf,strn(HTTPVER_10)) && /* no HTTP/1.[01]? */
491  strncmp(buf,strn(HTTPVER_11)))
492  {
493  TRACE (1, ("Not a HTTP/1.[01] server\n"));
494  return (FALSE);
495  }
496 
497  s = buf + sizeof(HTTPVER_10)-1;
498  i = strspn (s, " \t");
499  if (i == 0) /* no whitespace? */
500  {
501  TRACE (1, ("Malformed HTTP/1.[01] line\n"));
502  return (FALSE);
503  }
504 
505  s += i;
506  response = 500;
507  sscanf (s, "%3d", &response);
508  if (response == 401)
509  {
510  TRACE (1, ("Authorisation failed\n"));
511  return (FALSE);
512  }
513  if (response != 200 && response != 301 &&
514  response != 302 && response != 304)
515  {
516  TRACE (1, ("Unexpected response %s\n", s));
517  return (FALSE);
518  }
519 
520  /* Eat up the other header lines here.
521  * Set to LONG_MAX, in case no "Content-length" header.
522  */
523  content_length = LONG_MAX;
524 
525  while ((len = sock_gets(sock, (BYTE*)buf, sizeof(buf))) > 0)
526  {
527  TRACE (1, ("HTTP: `%.*s'\r\n", len, buf));
528  s = buf;
529  if (!strnicmp(s,strn("Content-Length:")))
530  {
531  s += sizeof("Content-Length:") - 1;
532  content_length = atol (s);
533  }
534 #if 0
535  else if (!strnicmp(s,strn("Transfer-encoding: chunked")))
536  {
537  TRACE (1, ("Chunked encoding not supported\n"));
538  return (FALSE);
539  }
540 #endif
541  else if (!strnicmp(buf, strn("Location:")))
542  {
543  if (response == 301 || response == 302)
544  TRACE (1, ("Location at %s\n", buf));
545  }
546  else if (strchr(" \t", buf[0]))
547  TRACE (1, ("Warning: continuation line encountered\n"));
548  }
549  *cont_len_ptr = content_length;
550  return (TRUE);
551 }
552 
556 static int get_url (const char *host, int port, const char *path,
557  const char *user_pass)
558 {
559  DWORD addr;
560  int status = 0;
561  int length;
562  long content_length = 0L;
563  char req_buf [512], *p;
564  sock_type *sock;
565 
566  if (!host)
567  {
568  TRACE (1, ("get_url: NULL host\n"));
569  return (-1);
570  }
571 
572  addr = resolve (host);
573  if (!addr)
574  {
575  TRACE (1, ("%s\n", dom_strerror(dom_errno)));
576  return (-1);
577  }
578 
579  sock = malloc (sizeof(*sock));
580  if (!sock)
581  {
582  puts ("get_url: No memory");
583  return (-1);
584  }
585 
586  if (!tcp_open(&sock->tcp, 0, addr, port, NULL))
587  {
588  printf ("Cannot connect to `%s'\n", host);
589  free (sock);
590  return (-1);
591  }
592 
593  length = 0;
594 
595  sock_wait_established (sock, 10, NULL, &status);
596  if (!tcp_tick(sock)) /* in case they sent reset */
597  {
598  status = -1;
599  goto sock_err;
600  }
601 
602  p = req_buf;
603  p += _SNPRINTF (BUF(req_buf),
604  "GET %s %s\r\n"
605  "Host: %s\r\n"
606  "User-Agent: %s\r\n",
607  path, HTTPVER, host, USER_AGENT);
608  if (user_pass)
609  {
610  char encoded_auth [256];
611 
612  if (base64encode(user_pass, encoded_auth, sizeof(encoded_auth)) < 0)
613  goto sock_err;
614  p += sprintf (p, "Authorization: Basic %s\r\n", encoded_auth);
615  }
616 
617  p += sprintf (p, "Connection: close\r\n\r\n");
618 
619  TRACE (2, ("Sent: %s\n", req_buf));
620 
621  sock_write (sock, (BYTE*)req_buf, (int)(p - req_buf));
622  sock_wait_input (sock, 10, NULL, &status);
623 
624  if (get_header (sock, &content_length))
625  TRACE (2, ("Content-length %ld, remaining %u\n",
626  content_length, sock_dataready(sock)));
627 
628  if (content_length >= 0L)
629  {
630  while (sock_dataready(sock) > 0)
631  {
632  int i = sock_gets (sock, (BYTE*)(resp_buf+length),
633  sizeof(resp_buf)-1-length);
634  TRACE (1, ("%s", resp_buf+length));
635  length += i;
636  if (length > SIZEOF(resp_buf))
637  {
638  length = SIZEOF(resp_buf);
639  break;
640  }
641  }
642  if (content_length != LONG_MAX && length != content_length)
643  TRACE (1, ("Warning, actual length = %d, content length = %ld\n",
644  length, content_length));
645  }
646 
647 sock_err:
648  sock_abort (sock); /* Kill 'sock' from the '_tcp_allsocs' list. */
649 
650  if (status == -1)
651  TRACE (1, ("get_url(): %s, %s, ", host, sockerr(sock)));
652  TRACE (2, ("got %d bytes\n", length));
653  free (sock);
654  return (length);
655 }
656 #endif /* USE_DYNIP_CLI */
657 
int W32_CALL parse_config_table(const struct config_table *tab, const char *section, const char *name, const char *value)
Parse the config-table and if a match is found for ('section'+'.
Definition: pcconfig.c:379
int W32_CALL tcp_open(_tcp_Socket *s, WORD lport, DWORD ip, WORD rport, ProtoHandler handler)
Actively open a TCP connection.
Definition: pctcp.c:308
DWORD W32_CALL aton(const char *str)
Converts [a.b.c.d] or a.b.c.d to 32 bit IPv4 address.
Definition: netaddr.c:86
convert to int
Definition: tcp.h:424
call convertion function
Definition: tcp.h:434
int W32_CALL sock_abort(sock_type *s)
Abort a UDP/TCP/Raw socket.
Definition: pctcp.c:2823
Definition: dynip.c:52
static void url_free(struct URL *url)
Free the URL.
Definition: dynip.c:354
copy string value
Definition: tcp.h:432
static int parse_dyndns_response(const char *buf)
Parse the status response from DynDNS.
Definition: dynip.c:267
Core definitions.
static int dyndns_params(const char *value)
Extracts the host, port and request from the value.
Definition: dynip.c:384
int W32_CALL sock_write(sock_type *s, const BYTE *data, int len)
Writes data and returns length written.
Definition: pctcp.c:2960
char * _strlcpy(char *dst, const char *src, size_t len)
Similar to strncpy(), but always returns 'dst' with 0-termination.
Definition: strings.c:226
static void dynip_exit(void)
Free allocated memory.
Definition: dynip.c:115
static BOOL url_parse(struct URL *res, const char *url)
Simple URL parser; accepts only "http://" prefixes (optional).
Definition: dynip.c:299
BOOL W32_CALL isaddr(const char *str)
Check if 'str' is simply an ip address.
Definition: netaddr.c:128
static const char * parse_myip_address(const char *buf)
Parse response buffer and find the first IP-address.
Definition: dynip.c:215
static void W32_CALL dynip_config(const char *name, const char *value)
Parser for DYNIP configuration.
Definition: dynip.c:93
static BOOL get_header(void *sock, long *cont_len_ptr)
Receive and parse the HYTTP header.
Definition: dynip.c:472
const char *W32_CALL dom_strerror(int err)
Return text for error code (dom_errno).
Definition: pcdns.c:746
BOOL _watt_fatal_error
Definition: misc.c:60
WORD W32_CALL tcp_tick(sock_type *s)
Must be called periodically by user application (or BSD socket API).
Definition: pctcp.c:1389
static int chkip_method(const char *value)
Select method of getting your public IP (WAN-side) address.
Definition: dynip.c:368
DWORD W32_CALL resolve(const char *name)
Convert host name to an address.
Definition: pcdns.c:775
WORD W32_CALL sock_dataready(sock_type *s)
sock_dataready - returns number of bytes waiting to be read.
Definition: sock_io.c:246
static const char *static int get_url(const char *host, int port, const char *path, const char *user_pass)
Fetch a single URL optionally with authentication.
Definition: dynip.c:556