Back to index

citadel  8.12
locate_host.c
Go to the documentation of this file.
00001 /*
00002  * Functions which handle hostname/address lookups and resolution
00003  *
00004  * Copyright (c) 1987-2011 by the citadel.org team
00005  *
00006  * This program is open source software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 3 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
00019  */
00020 
00021 #include "sysdep.h"
00022 #include <stdlib.h>
00023 #include <unistd.h>
00024 #include <stdio.h>
00025 #include <ctype.h>
00026 #include <signal.h>
00027 #include <sys/types.h>
00028 #include <sys/socket.h>
00029 #include <netinet/in.h>
00030 #include <arpa/inet.h>
00031 #include <limits.h>
00032 #include <netdb.h>
00033 #include <string.h>
00034 #include <errno.h>
00035 #include <syslog.h>
00036 #include <libcitadel.h>
00037 #include "citadel.h"
00038 #include "server.h"
00039 #include "locate_host.h"
00040 #include "sysdep_decls.h"
00041 #include "config.h"
00042 #include "domain.h"
00043 #include "context.h"
00044 #include "ctdl_module.h"
00045 
00046 #ifdef HAVE_RESOLV_H
00047 #include <arpa/nameser.h>
00048 #ifdef HAVE_ARPA_NAMESER_COMPAT_H
00049 #include <arpa/nameser_compat.h>
00050 #endif
00051 #include <resolv.h>
00052 #endif
00053 
00055 #ifndef NS_CMPRSFLGS
00056 #define NS_CMPRSFLGS   0xc0
00057 #endif
00058 #if !defined(NS_MAXCDNAME) && defined (MAXCDNAME)
00059 #define NS_MAXCDNAME MAXCDNAME
00060 #endif
00061 #if !defined(NS_INT16SZ) && defined(INT16SZ)
00062 #define NS_INT16SZ INT16SZ
00063 #define NS_INT32SZ INT32SZ
00064 #endif
00065 #ifndef NS_GET16
00066 #  define NS_GET16 GETSHORT
00067 #endif
00068 
00071 /*
00072  * Given an open client socket, return the host name and IP address at the other end.
00073  * (IPv4 and IPv6 compatible)
00074  */
00075 void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket)
00076 {
00077        struct sockaddr_in6 clientaddr;
00078        unsigned int addrlen = sizeof(clientaddr);
00079 
00080        tbuf[0] = 0;
00081        abuf[0] = 0;
00082 
00083        getpeername(client_socket, (struct sockaddr *)&clientaddr, &addrlen);
00084        getnameinfo((struct sockaddr *)&clientaddr, addrlen, tbuf, n, NULL, 0, 0);
00085        getnameinfo((struct sockaddr *)&clientaddr, addrlen, abuf, na, NULL, 0, NI_NUMERICHOST);
00086 
00087        /* Convert IPv6-mapped IPv4 addresses back to traditional dotted quad.
00088         *
00089         * Other code here, such as the RBL check, will expect IPv4 addresses to be represented
00090         * as dotted-quad, even if they come in over a hybrid IPv6/IPv4 socket.
00091         */
00092        if ( (strlen(abuf) > 7) && (!strncasecmp(abuf, "::ffff:", 7)) ) {
00093               if (!strcmp(abuf, tbuf)) strcpy(tbuf, &tbuf[7]);
00094               strcpy(abuf, &abuf[7]);
00095        }
00096 }
00097 
00098 
00099 /*
00100  * RBL check written by Edward S. Marshall [http://rblcheck.sourceforge.net]
00101  */
00102 #define RESULT_SIZE 4096 /* What is the longest result text we support? */
00103 int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize) {
00104        int a, b, c;
00105        char *result = NULL;
00106        u_char fixedans[ PACKETSZ ];
00107        u_char *answer;
00108        int need_to_free_answer = 0;
00109        const u_char *cp;
00110        u_char *rp;
00111        const u_char *cend;
00112        const u_char *rend;
00113        int len;
00114        char *p = NULL;
00115        static int res_initted = 0;
00116 
00117        if (!res_initted) {         /* only have to do this once */
00118               res_init();
00119               res_initted = 1;
00120        }
00121 
00122        /* Make our DNS query. */
00123        answer = fixedans;
00124        if (server_shutting_down)
00125        {
00126               if (txtbuf != NULL) {
00127                      snprintf(txtbuf, txtbufsize, "System shutting down");
00128               }
00129               return (1);
00130        }
00131        len = res_query(domain, C_IN, T_A, answer, PACKETSZ);
00132 
00133        /* Was there a problem? If so, the domain doesn't exist. */
00134        if (len == -1) {
00135               if (txtbuf != NULL) {
00136                      strcpy(txtbuf, "");
00137               }
00138               return(0);
00139        }
00140 
00141        if( len > PACKETSZ )
00142        {
00143               answer = malloc(len);
00144               need_to_free_answer = 1;
00145               len = res_query(domain, C_IN, T_A, answer, len);
00146               if( len == -1 ) {
00147                      if (txtbuf != NULL) {
00148                             snprintf(txtbuf, txtbufsize,
00149                                     "Message rejected due to known spammer source IP address");
00150                      }
00151                      if (need_to_free_answer) free(answer);
00152                      return(1);
00153               }
00154        }
00155        if (server_shutting_down)
00156        {
00157               if (txtbuf != NULL)
00158                      snprintf(txtbuf, txtbufsize, "System shutting down");
00159               if (need_to_free_answer) free(answer);
00160               return (1);
00161        }
00162 
00163        result = (char *) malloc(RESULT_SIZE);
00164        result[ 0 ] = '\0';
00165 
00166 
00167        /* Make another DNS query for textual data; this shouldn't
00168         * be a performance hit, since it'll now be cached at the
00169         * nameserver we're using.
00170         */
00171        len = res_query(domain, C_IN, T_TXT, answer, PACKETSZ);
00172        if (server_shutting_down)
00173        {
00174               if (txtbuf != NULL) {
00175                      snprintf(txtbuf, txtbufsize, "System shutting down");
00176               }
00177               if (need_to_free_answer) free(answer);
00178               free(result);
00179               return (1);
00180        }
00181 
00182        /* Just in case there's no TXT record... */
00183        if (len ==(-1))
00184        {
00185               if (txtbuf != NULL) {
00186                      snprintf(txtbuf, txtbufsize,
00187                              "Message rejected due to known spammer source IP address");
00188               }
00189               if (need_to_free_answer) free(answer);
00190               free(result);
00191               return(1);
00192        }
00193 
00194        /* Skip the header and the address we queried. */
00195        cp = answer + sizeof( HEADER );
00196        while( *cp != '\0' )
00197        {
00198               a = *cp++;
00199               while( a-- )
00200                      cp++;
00201        }
00202 
00203        /* This seems to be a bit of magic data that we need to
00204         * skip. I wish there were good online documentation
00205         * for programming for libresolv, so I'd know what I'm
00206         * skipping here. Anyone reading this, feel free to
00207         * enlighten me.
00208         */
00209        cp += 1 + NS_INT16SZ + NS_INT32SZ;
00210 
00211        /* Skip the type, class and ttl. */
00212        cp += (NS_INT16SZ * 2) + NS_INT32SZ;
00213 
00214        /* Get the length and end of the buffer. */
00215        NS_GET16(c, cp);
00216        cend = cp + c;
00217 
00218        /* Iterate over any multiple answers we might have. In
00219         * this context, it's unlikely, but anyway.
00220         */
00221        rp = (u_char *) result;
00222        rend = (u_char *) result + RESULT_SIZE - 1;
00223        while (cp < cend && rp < rend)
00224        {
00225               a = *cp++;
00226               if( a != 0 )
00227                      for (b = a; b > 0 && cp < cend && rp < rend; b--)
00228                      {
00229                             if (*cp == '\n' || *cp == '"' || *cp == '\\')
00230                             {
00231                                    *rp++ = '\\';
00232                             }
00233                             *rp++ = *cp++;
00234                      }
00235        }
00236        *rp = '\0';
00237        if (txtbuf != NULL) {
00238               long len;
00239               len = snprintf(txtbuf, txtbufsize, "%s", result);
00240        
00241               /* Remove nonprintable characters */
00242               for (p = txtbuf; *p != '\0'; p++) {
00243                      if (!isprint(*p)) {
00244                             memmove (p,
00245                                     p + 1,
00246                                     len - (p - txtbuf) - 1);
00247                      }
00248               }
00249        }
00250        if (need_to_free_answer) free(answer);
00251        free(result);
00252        return(1);
00253 }
00254 
00255 
00256 /*
00257  * Check to see if the client host is on some sort of spam list (RBL)
00258  * If spammer, returns nonzero and places reason in 'message_to_spammer'
00259  */
00260 int rbl_check(char *message_to_spammer)
00261 {
00262        char tbuf[256] = "";
00263        int suffix_pos = 0;
00264        int rbl;
00265        int num_rbl;
00266        char rbl_domains[SIZ];
00267        char txt_answer[1024];
00268 
00269        strcpy(message_to_spammer, "ok");
00270 
00271        if ((strchr(CC->cs_addr, '.')) && (!strchr(CC->cs_addr, ':'))) {
00272               int a1, a2, a3, a4;
00273 
00274               sscanf(CC->cs_addr, "%d.%d.%d.%d", &a1, &a2, &a3, &a4);
00275               snprintf(tbuf, sizeof tbuf, "%d.%d.%d.%d.", a4, a3, a2, a1);
00276               suffix_pos = strlen(tbuf);
00277        }
00278        else if ((!strchr(CC->cs_addr, '.')) && (strchr(CC->cs_addr, ':'))) {
00279               int num_colons = 0;
00280               int i = 0;
00281               char workbuf[sizeof tbuf];
00282               char *ptr;
00283 
00284               /* tedious code to expand and reverse an IPv6 address */
00285               safestrncpy(tbuf, CC->cs_addr, sizeof tbuf);
00286               num_colons = haschar(tbuf, ':');
00287               if ((num_colons < 2) || (num_colons > 7)) return(0);    /* badly formed address */
00288 
00289               /* expand the "::" shorthand */
00290               while (num_colons < 7) {
00291                      ptr = strstr(tbuf, "::");
00292                      if (!ptr) return(0);                      /* badly formed address */
00293                      ++ptr;
00294                      strcpy(workbuf, ptr);
00295                      strcpy(ptr, ":");
00296                      strcat(ptr, workbuf);
00297                      ++num_colons;
00298               }
00299 
00300               /* expand to 32 hex characters with no colons */
00301               strcpy(workbuf, tbuf);
00302               strcpy(tbuf, "00000000000000000000000000000000");
00303               for (i=0; i<8; ++i) {
00304                      char tokbuf[5];
00305                      extract_token(tokbuf, workbuf, i, ':', sizeof tokbuf);
00306 
00307                      memcpy(&tbuf[ (i*4) + (4-strlen(tokbuf)) ], tokbuf, strlen(tokbuf) );
00308               }
00309               if (strlen(tbuf) != 32) return(0);
00310 
00311               /* now reverse it and add dots */
00312               strcpy(workbuf, tbuf);
00313               for (i=0; i<32; ++i) {
00314                      tbuf[i*2] = workbuf[31-i];
00315                      tbuf[(i*2)+1] = '.';
00316               }
00317               tbuf[64] = 0;
00318               suffix_pos = 64;
00319        }
00320        else {
00321               return(0);    /* unknown address format */
00322        }
00323 
00324        /* See if we have any RBL domains configured */
00325        num_rbl = get_hosts(rbl_domains, "rbl");
00326        if (num_rbl < 1) return(0);
00327 
00328        /* Try all configured RBL's */
00329         for (rbl=0; rbl<num_rbl; ++rbl) {
00330                 extract_token(&tbuf[suffix_pos], rbl_domains, rbl, '|', (sizeof tbuf - suffix_pos));
00331 
00332               if (rblcheck_backend(tbuf, txt_answer, sizeof txt_answer)) {
00333                      strcpy(message_to_spammer, txt_answer);
00334                      syslog(LOG_INFO, "RBL: %s\n", txt_answer);
00335                      return(1);
00336               }
00337        }
00338 
00339        return(0);
00340 }
00341                      
00342 
00343 /*
00344  * Convert a host name to a dotted quad address. 
00345  * Returns zero on success or nonzero on failure.
00346  *
00347  * FIXME this is obviously not IPv6 compatible.
00348  */
00349 int hostname_to_dotted_quad(char *addr, char *host) {
00350        struct hostent *ch;
00351        const char *i;
00352        int a1, a2, a3, a4;
00353 
00354        ch = gethostbyname(host);
00355        if (ch == NULL) {
00356               strcpy(addr, "0.0.0.0");
00357               return(1);
00358        }
00359 
00360        i = (const char *) ch->h_addr_list[0];
00361        a1 = ((*i++) & 0xff);
00362        a2 = ((*i++) & 0xff);
00363        a3 = ((*i++) & 0xff);
00364        a4 = ((*i++) & 0xff);
00365        sprintf(addr, "%d.%d.%d.%d", a1, a2, a3, a4);
00366        return(0);
00367 }