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