1 /* $NetBSD: dict_tcp.c,v 1.2 2017/02/14 01:16:49 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_tcp 3 6 /* SUMMARY 7 /* dictionary manager interface to tcp-based lookup tables 8 /* SYNOPSIS 9 /* #include <dict_tcp.h> 10 /* 11 /* DICT *dict_tcp_open(map, open_flags, dict_flags) 12 /* const char *map; 13 /* int open_flags; 14 /* int dict_flags; 15 /* DESCRIPTION 16 /* dict_tcp_open() makes a TCP server accessible via the generic 17 /* dictionary operations described in dict_open(3). 18 /* The only implemented operation is dictionary lookup. This map 19 /* type can be useful for simulating a dynamic lookup table. 20 /* 21 /* Map names have the form host:port. 22 /* 23 /* The TCP map class implements a very simple protocol: the client 24 /* sends a request, and the server sends one reply. Requests and 25 /* replies are sent as one line of ASCII text, terminated by the 26 /* ASCII newline character. Request and reply parameters (see below) 27 /* are separated by whitespace. 28 /* ENCODING 29 /* .ad 30 /* .fi 31 /* In request and reply parameters, the character % and any non-printing 32 /* and whitespace characters must be replaced by %XX, XX being the 33 /* corresponding ASCII hexadecimal character value. The hexadecimal codes 34 /* can be specified in any case (upper, lower, mixed). 35 /* REQUEST FORMAT 36 /* .ad 37 /* .fi 38 /* Requests are strings that serve as lookup key in the simulated 39 /* table. 40 /* .IP "get SPACE key NEWLINE" 41 /* Look up data under the specified key. 42 /* .IP "put SPACE key SPACE value NEWLINE" 43 /* This request is currently not implemented. 44 /* REPLY FORMAT 45 /* .ad 46 /* .fi 47 /* Replies must be no longer than 4096 characters including the 48 /* newline terminator, and must have the following form: 49 /* .IP "500 SPACE text NEWLINE" 50 /* In case of a lookup request, the requested data does not exist. 51 /* In case of an update request, the request was rejected. 52 /* The text gives the nature of the problem. 53 /* .IP "400 SPACE text NEWLINE" 54 /* This indicates an error condition. The text gives the nature of 55 /* the problem. The client should retry the request later. 56 /* .IP "200 SPACE text NEWLINE" 57 /* The request was successful. In the case of a lookup request, 58 /* the text contains an encoded version of the requested data. 59 /* SECURITY 60 /* This map must not be used for security sensitive information, 61 /* because neither the connection nor the server are authenticated. 62 /* SEE ALSO 63 /* dict(3) generic dictionary manager 64 /* hex_quote(3) http-style quoting 65 /* DIAGNOSTICS 66 /* Fatal errors: out of memory, unknown host or service name, 67 /* attempt to update or iterate over map. 68 /* BUGS 69 /* Only the lookup method is currently implemented. 70 /* LICENSE 71 /* .ad 72 /* .fi 73 /* The Secure Mailer license must be distributed with this software. 74 /* AUTHOR(S) 75 /* Wietse Venema 76 /* IBM T.J. Watson Research 77 /* P.O. Box 704 78 /* Yorktown Heights, NY 10598, USA 79 /*--*/ 80 81 /* System library. */ 82 83 #include "sys_defs.h" 84 #include <unistd.h> 85 #include <string.h> 86 #include <errno.h> 87 #include <ctype.h> 88 89 /* Utility library. */ 90 91 #include <msg.h> 92 #include <mymalloc.h> 93 #include <vstring.h> 94 #include <vstream.h> 95 #include <vstring_vstream.h> 96 #include <connect.h> 97 #include <hex_quote.h> 98 #include <dict.h> 99 #include <stringops.h> 100 #include <dict_tcp.h> 101 102 /* Application-specific. */ 103 104 typedef struct { 105 DICT dict; /* generic members */ 106 VSTRING *raw_buf; /* raw I/O buffer */ 107 VSTRING *hex_buf; /* quoted I/O buffer */ 108 VSTREAM *fp; /* I/O stream */ 109 } DICT_TCP; 110 111 #define DICT_TCP_MAXTRY 10 /* attempts before giving up */ 112 #define DICT_TCP_TMOUT 100 /* connect/read/write timeout */ 113 #define DICT_TCP_MAXLEN 4096 /* server reply size limit */ 114 115 #define STR(x) vstring_str(x) 116 117 /* dict_tcp_connect - connect to TCP server */ 118 119 static int dict_tcp_connect(DICT_TCP *dict_tcp) 120 { 121 int fd; 122 123 /* 124 * Connect to the server. Enforce a time limit on all operations so that 125 * we do not get stuck. 126 */ 127 if ((fd = inet_connect(dict_tcp->dict.name, NON_BLOCKING, DICT_TCP_TMOUT)) < 0) { 128 msg_warn("connect to TCP map %s: %m", dict_tcp->dict.name); 129 return (-1); 130 } 131 dict_tcp->fp = vstream_fdopen(fd, O_RDWR); 132 vstream_control(dict_tcp->fp, 133 CA_VSTREAM_CTL_TIMEOUT(DICT_TCP_TMOUT), 134 CA_VSTREAM_CTL_END); 135 136 /* 137 * Allocate per-map I/O buffers on the fly. 138 */ 139 if (dict_tcp->raw_buf == 0) { 140 dict_tcp->raw_buf = vstring_alloc(10); 141 dict_tcp->hex_buf = vstring_alloc(10); 142 } 143 return (0); 144 } 145 146 /* dict_tcp_disconnect - disconnect from TCP server */ 147 148 static void dict_tcp_disconnect(DICT_TCP *dict_tcp) 149 { 150 (void) vstream_fclose(dict_tcp->fp); 151 dict_tcp->fp = 0; 152 } 153 154 /* dict_tcp_lookup - request TCP server */ 155 156 static const char *dict_tcp_lookup(DICT *dict, const char *key) 157 { 158 DICT_TCP *dict_tcp = (DICT_TCP *) dict; 159 const char *myname = "dict_tcp_lookup"; 160 int tries; 161 char *start; 162 int last_ch; 163 164 #define RETURN(errval, result) { dict->error = errval; return (result); } 165 166 if (msg_verbose) 167 msg_info("%s: key %s", myname, key); 168 169 /* 170 * Optionally fold the key. 171 */ 172 if (dict->flags & DICT_FLAG_FOLD_MUL) { 173 if (dict->fold_buf == 0) 174 dict->fold_buf = vstring_alloc(10); 175 vstring_strcpy(dict->fold_buf, key); 176 key = lowercase(vstring_str(dict->fold_buf)); 177 } 178 for (tries = 0; /* see below */ ; /* see below */ ) { 179 180 /* 181 * Connect to the server, or use an existing connection. 182 */ 183 if (dict_tcp->fp != 0 || dict_tcp_connect(dict_tcp) == 0) { 184 185 /* 186 * Send request and receive response. Both are %XX quoted and 187 * both are terminated by newline. This encoding is convenient 188 * for data that is mostly text. 189 */ 190 hex_quote(dict_tcp->hex_buf, key); 191 vstream_fprintf(dict_tcp->fp, "get %s\n", STR(dict_tcp->hex_buf)); 192 if (msg_verbose) 193 msg_info("%s: send: get %s", myname, STR(dict_tcp->hex_buf)); 194 last_ch = vstring_get_nonl_bound(dict_tcp->hex_buf, dict_tcp->fp, 195 DICT_TCP_MAXLEN); 196 if (last_ch == '\n') 197 break; 198 199 /* 200 * Disconnect from the server if it can't talk to us. 201 */ 202 if (last_ch < 0) 203 msg_warn("read TCP map reply from %s: unexpected EOF (%m)", 204 dict_tcp->dict.name); 205 else 206 msg_warn("read TCP map reply from %s: text longer than %d", 207 dict_tcp->dict.name, DICT_TCP_MAXLEN); 208 dict_tcp_disconnect(dict_tcp); 209 } 210 211 /* 212 * Try to connect a limited number of times before giving up. 213 */ 214 if (++tries >= DICT_TCP_MAXTRY) 215 RETURN(DICT_ERR_RETRY, 0); 216 217 /* 218 * Sleep between attempts, instead of hammering the server. 219 */ 220 sleep(1); 221 } 222 if (msg_verbose) 223 msg_info("%s: recv: %s", myname, STR(dict_tcp->hex_buf)); 224 225 /* 226 * Check the general reply syntax. If the reply is malformed, disconnect 227 * and try again later. 228 */ 229 if (start = STR(dict_tcp->hex_buf), 230 !ISDIGIT(start[0]) || !ISDIGIT(start[1]) 231 || !ISDIGIT(start[2]) || !ISSPACE(start[3]) 232 || !hex_unquote(dict_tcp->raw_buf, start + 4)) { 233 msg_warn("read TCP map reply from %s: malformed reply: %.100s", 234 dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); 235 dict_tcp_disconnect(dict_tcp); 236 RETURN(DICT_ERR_RETRY, 0); 237 } 238 239 /* 240 * Examine the reply status code. If the reply is malformed, disconnect 241 * and try again later. 242 */ 243 switch (start[0]) { 244 default: 245 msg_warn("read TCP map reply from %s: bad status code: %.100s", 246 dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); 247 dict_tcp_disconnect(dict_tcp); 248 RETURN(DICT_ERR_RETRY, 0); 249 case '4': 250 if (msg_verbose) 251 msg_info("%s: soft error: %s", 252 myname, printable(STR(dict_tcp->hex_buf), '_')); 253 dict_tcp_disconnect(dict_tcp); 254 RETURN(DICT_ERR_RETRY, 0); 255 case '5': 256 if (msg_verbose) 257 msg_info("%s: not found: %s", 258 myname, printable(STR(dict_tcp->hex_buf), '_')); 259 RETURN(DICT_ERR_NONE, 0); 260 case '2': 261 if (msg_verbose) 262 msg_info("%s: found: %s", 263 myname, printable(STR(dict_tcp->raw_buf), '_')); 264 RETURN(DICT_ERR_NONE, STR(dict_tcp->raw_buf)); 265 } 266 } 267 268 /* dict_tcp_close - close TCP map */ 269 270 static void dict_tcp_close(DICT *dict) 271 { 272 DICT_TCP *dict_tcp = (DICT_TCP *) dict; 273 274 if (dict_tcp->fp) 275 (void) vstream_fclose(dict_tcp->fp); 276 if (dict_tcp->raw_buf) 277 vstring_free(dict_tcp->raw_buf); 278 if (dict_tcp->hex_buf) 279 vstring_free(dict_tcp->hex_buf); 280 if (dict->fold_buf) 281 vstring_free(dict->fold_buf); 282 dict_free(dict); 283 } 284 285 /* dict_tcp_open - open TCP map */ 286 287 DICT *dict_tcp_open(const char *map, int open_flags, int dict_flags) 288 { 289 DICT_TCP *dict_tcp; 290 291 /* 292 * Sanity checks. 293 */ 294 if (dict_flags & DICT_FLAG_NO_UNAUTH) 295 return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags, 296 "%s:%s map is not allowed for security sensitive data", 297 DICT_TYPE_TCP, map)); 298 if (open_flags != O_RDONLY) 299 return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags, 300 "%s:%s map requires O_RDONLY access mode", 301 DICT_TYPE_TCP, map)); 302 303 /* 304 * Create the dictionary handle. Do not open the connection until the 305 * first request is made. 306 */ 307 dict_tcp = (DICT_TCP *) dict_alloc(DICT_TYPE_TCP, map, sizeof(*dict_tcp)); 308 dict_tcp->fp = 0; 309 dict_tcp->raw_buf = dict_tcp->hex_buf = 0; 310 dict_tcp->dict.lookup = dict_tcp_lookup; 311 dict_tcp->dict.close = dict_tcp_close; 312 dict_tcp->dict.flags = dict_flags | DICT_FLAG_PATTERN; 313 if (dict_flags & DICT_FLAG_FOLD_MUL) 314 dict_tcp->dict.fold_buf = vstring_alloc(10); 315 316 return (DICT_DEBUG (&dict_tcp->dict)); 317 } 318