1 /* $OpenBSD: makemap.c,v 1.65 2016/03/17 19:40:43 krw Exp $ */ 2 3 /* 4 * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> 5 * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/types.h> 21 #include <sys/stat.h> 22 #include <sys/tree.h> 23 #include <sys/queue.h> 24 #include <sys/socket.h> 25 26 #include <ctype.h> 27 #include <db.h> 28 #include <err.h> 29 #include <errno.h> 30 #include <event.h> 31 #include <fcntl.h> 32 #include <imsg.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <unistd.h> 37 #include <limits.h> 38 #include <util.h> 39 40 #include "smtpd.h" 41 #include "log.h" 42 43 #define PATH_ALIASES "/etc/mail/aliases" 44 45 static void usage(void); 46 static int parse_map(DB *, int *, char *); 47 static int parse_entry(DB *, int *, char *, size_t, size_t); 48 static int parse_mapentry(DB *, int *, char *, size_t, size_t); 49 static int parse_setentry(DB *, int *, char *, size_t, size_t); 50 static int make_plain(DBT *, char *); 51 static int make_aliases(DBT *, char *); 52 static char *conf_aliases(char *); 53 static int dump_db(const char *, DBTYPE); 54 55 struct smtpd smtpd; 56 struct smtpd *env = &smtpd; 57 char *source; 58 extern char *__progname; 59 60 enum program { 61 P_MAKEMAP, 62 P_NEWALIASES 63 } mode; 64 65 enum output_type { 66 T_PLAIN, 67 T_ALIASES, 68 T_SET 69 } type; 70 71 /* 72 * Stub functions so that makemap compiles using minimum object files. 73 */ 74 void 75 purge_config(uint8_t what) 76 { 77 memset(env, 0, sizeof(struct smtpd)); 78 } 79 80 int 81 fork_proc_backend(const char *backend, const char *conf, const char *procname) 82 { 83 return (-1); 84 } 85 86 int 87 makemap(int argc, char *argv[]) 88 { 89 struct stat sb; 90 char dbname[PATH_MAX]; 91 DB *db; 92 const char *opts; 93 char *conf, *oflag = NULL; 94 int ch, dbputs = 0, Uflag = 0; 95 DBTYPE dbtype = DB_HASH; 96 char *p; 97 int fd = -1; 98 99 log_init(1); 100 101 mode = strcmp(__progname, "newaliases") ? P_MAKEMAP : P_NEWALIASES; 102 conf = CONF_FILE; 103 type = T_PLAIN; 104 opts = "b:C:d:ho:O:t:U"; 105 if (mode == P_NEWALIASES) 106 opts = "f:h"; 107 108 while ((ch = getopt(argc, argv, opts)) != -1) { 109 switch (ch) { 110 case 'b': 111 if (optarg && strcmp(optarg, "i") == 0) 112 mode = P_NEWALIASES; 113 break; 114 case 'C': 115 break; /* for compatibility */ 116 case 'd': 117 if (strcmp(optarg, "hash") == 0) 118 dbtype = DB_HASH; 119 else if (strcmp(optarg, "btree") == 0) 120 dbtype = DB_BTREE; 121 else 122 errx(1, "unsupported DB type '%s'", optarg); 123 break; 124 case 'f': 125 conf = optarg; 126 break; 127 case 'o': 128 oflag = optarg; 129 break; 130 case 'O': 131 if (strncmp(optarg, "AliasFile=", 10) != 0) 132 break; 133 type = T_ALIASES; 134 p = strchr(optarg, '='); 135 source = ++p; 136 break; 137 case 't': 138 if (strcmp(optarg, "aliases") == 0) 139 type = T_ALIASES; 140 else if (strcmp(optarg, "set") == 0) 141 type = T_SET; 142 else 143 errx(1, "unsupported type '%s'", optarg); 144 break; 145 case 'U': 146 Uflag = 1; 147 break; 148 default: 149 usage(); 150 } 151 } 152 argc -= optind; 153 argv += optind; 154 155 /* sendmail-compat makemap ... re-execute using proper interface */ 156 if (argc == 2) { 157 if (oflag) 158 usage(); 159 160 p = strstr(argv[1], ".db"); 161 if (p == NULL || strcmp(p, ".db") != 0) { 162 if (!bsnprintf(dbname, sizeof dbname, "%s.db", 163 argv[1])) 164 errx(1, "database name too long"); 165 } 166 else { 167 if (strlcpy(dbname, argv[1], sizeof dbname) 168 >= sizeof dbname) 169 errx(1, "database name too long"); 170 } 171 172 execlp("makemap", "makemap", "-d", argv[0], "-o", dbname, "-", 173 (char *)NULL); 174 err(1, "execlp"); 175 } 176 177 if (mode == P_NEWALIASES) { 178 if (geteuid()) 179 errx(1, "need root privileges"); 180 if (argc != 0) 181 usage(); 182 type = T_ALIASES; 183 if (source == NULL) 184 source = conf_aliases(conf); 185 } else { 186 if (argc != 1) 187 usage(); 188 source = argv[0]; 189 } 190 191 if (Uflag) 192 return dump_db(source, dbtype); 193 194 if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1) 195 err(1, "asprintf"); 196 197 if (strcmp(source, "-") != 0) 198 if (stat(source, &sb) == -1) 199 err(1, "stat: %s", source); 200 201 if (!bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag)) 202 errx(1, "path too long"); 203 if ((fd = mkstemp(dbname)) == -1) 204 err(1, "mkstemp"); 205 206 db = dbopen(dbname, O_TRUNC|O_RDWR, 0644, dbtype, NULL); 207 if (db == NULL) { 208 warn("dbopen: %s", dbname); 209 goto bad; 210 } 211 212 if (strcmp(source, "-") != 0) 213 if (fchmod(db->fd(db), sb.st_mode) == -1 || 214 fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) { 215 warn("couldn't carry ownership and perms to %s", 216 dbname); 217 goto bad; 218 } 219 220 if (!parse_map(db, &dbputs, source)) 221 goto bad; 222 223 if (db->close(db) == -1) { 224 warn("dbclose: %s", dbname); 225 goto bad; 226 } 227 228 /* force to disk before renaming over an existing file */ 229 if (fsync(fd) == -1) { 230 warn("fsync: %s", dbname); 231 goto bad; 232 } 233 if (close(fd) == -1) { 234 fd = -1; 235 warn("close: %s", dbname); 236 goto bad; 237 } 238 fd = -1; 239 240 if (rename(dbname, oflag) == -1) { 241 warn("rename"); 242 goto bad; 243 } 244 245 if (mode == P_NEWALIASES) 246 printf("%s: %d aliases\n", source, dbputs); 247 else if (dbputs == 0) 248 warnx("warning: empty map created: %s", oflag); 249 250 return 0; 251 bad: 252 if (fd != -1) 253 close(fd); 254 unlink(dbname); 255 return 1; 256 } 257 258 static int 259 parse_map(DB *db, int *dbputs, char *filename) 260 { 261 FILE *fp; 262 char *line; 263 size_t len; 264 size_t lineno = 0; 265 266 if (strcmp(filename, "-") == 0) 267 fp = fdopen(0, "r"); 268 else 269 fp = fopen(filename, "r"); 270 if (fp == NULL) { 271 warn("%s", filename); 272 return 0; 273 } 274 275 if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) { 276 if (errno == EWOULDBLOCK) 277 warnx("%s is locked", filename); 278 else 279 warn("%s: flock", filename); 280 fclose(fp); 281 return 0; 282 } 283 284 while ((line = fparseln(fp, &len, &lineno, 285 NULL, FPARSELN_UNESCCOMM)) != NULL) { 286 if (!parse_entry(db, dbputs, line, len, lineno)) { 287 free(line); 288 fclose(fp); 289 return 0; 290 } 291 free(line); 292 } 293 294 fclose(fp); 295 return 1; 296 } 297 298 static int 299 parse_entry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) 300 { 301 switch (type) { 302 case T_PLAIN: 303 case T_ALIASES: 304 return parse_mapentry(db, dbputs, line, len, lineno); 305 case T_SET: 306 return parse_setentry(db, dbputs, line, len, lineno); 307 } 308 return 0; 309 } 310 311 static int 312 parse_mapentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) 313 { 314 DBT key; 315 DBT val; 316 char *keyp; 317 char *valp; 318 319 keyp = line; 320 while (isspace((unsigned char)*keyp)) 321 keyp++; 322 if (*keyp == '\0') 323 return 1; 324 325 valp = keyp; 326 strsep(&valp, " \t:"); 327 if (valp == NULL || valp == keyp) 328 goto bad; 329 while (*valp == ':' || isspace((unsigned char)*valp)) 330 valp++; 331 if (*valp == '\0') 332 goto bad; 333 334 /* Check for dups. */ 335 key.data = keyp; 336 key.size = strlen(keyp) + 1; 337 338 xlowercase(key.data, key.data, strlen(key.data) + 1); 339 if (db->get(db, &key, &val, 0) == 0) { 340 warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); 341 return 0; 342 } 343 344 if (type == T_PLAIN) { 345 if (!make_plain(&val, valp)) 346 goto bad; 347 } 348 else if (type == T_ALIASES) { 349 if (!make_aliases(&val, valp)) 350 goto bad; 351 } 352 353 if (db->put(db, &key, &val, 0) == -1) { 354 warn("dbput"); 355 return 0; 356 } 357 358 (*dbputs)++; 359 360 free(val.data); 361 362 return 1; 363 364 bad: 365 warnx("%s:%zd: invalid entry", source, lineno); 366 return 0; 367 } 368 369 static int 370 parse_setentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) 371 { 372 DBT key; 373 DBT val; 374 char *keyp; 375 376 keyp = line; 377 while (isspace((unsigned char)*keyp)) 378 keyp++; 379 if (*keyp == '\0') 380 return 1; 381 382 val.data = "<set>"; 383 val.size = strlen(val.data) + 1; 384 385 /* Check for dups. */ 386 key.data = keyp; 387 key.size = strlen(keyp) + 1; 388 xlowercase(key.data, key.data, strlen(key.data) + 1); 389 if (db->get(db, &key, &val, 0) == 0) { 390 warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); 391 return 0; 392 } 393 394 if (db->put(db, &key, &val, 0) == -1) { 395 warn("dbput"); 396 return 0; 397 } 398 399 (*dbputs)++; 400 401 return 1; 402 } 403 404 static int 405 make_plain(DBT *val, char *text) 406 { 407 val->data = xstrdup(text, "make_plain"); 408 val->size = strlen(text) + 1; 409 410 return (val->size); 411 } 412 413 static int 414 make_aliases(DBT *val, char *text) 415 { 416 struct expandnode xn; 417 char *subrcpt; 418 char *origtext; 419 420 val->data = NULL; 421 val->size = 0; 422 423 origtext = xstrdup(text, "make_aliases"); 424 425 while ((subrcpt = strsep(&text, ",")) != NULL) { 426 /* subrcpt: strip initial and trailing whitespace. */ 427 subrcpt = strip(subrcpt); 428 if (*subrcpt == '\0') 429 goto error; 430 431 if (!text_to_expandnode(&xn, subrcpt)) 432 goto error; 433 } 434 435 val->data = origtext; 436 val->size = strlen(origtext) + 1; 437 return (val->size); 438 439 error: 440 free(origtext); 441 442 return 0; 443 } 444 445 static char * 446 conf_aliases(char *cfgpath) 447 { 448 struct table *table; 449 char *path; 450 char *p; 451 452 if (parse_config(env, cfgpath, 0)) 453 exit(1); 454 455 table = table_find("aliases", NULL); 456 if (table == NULL) 457 return (PATH_ALIASES); 458 459 path = xstrdup(table->t_config, "conf_aliases"); 460 p = strstr(path, ".db"); 461 if (p == NULL || strcmp(p, ".db") != 0) { 462 return (path); 463 } 464 *p = '\0'; 465 return (path); 466 } 467 468 static int 469 dump_db(const char *dbname, DBTYPE dbtype) 470 { 471 DB *db; 472 DBT key, val; 473 char *keystr, *valstr; 474 int r; 475 476 db = dbopen(dbname, O_RDONLY, 0644, dbtype, NULL); 477 if (db == NULL) 478 err(1, "dbopen: %s", dbname); 479 480 for (r = db->seq(db, &key, &val, R_FIRST); r == 0; 481 r = db->seq(db, &key, &val, R_NEXT)) { 482 keystr = key.data; 483 valstr = val.data; 484 if (keystr[key.size - 1] == '\0') 485 key.size--; 486 if (valstr[val.size - 1] == '\0') 487 val.size--; 488 printf("%.*s\t%.*s\n", (int)key.size, keystr, 489 (int)val.size, valstr); 490 } 491 if (r == -1) 492 err(1, "db->seq: %s", dbname); 493 494 if (db->close(db) == -1) 495 err(1, "dbclose: %s", dbname); 496 497 return 0; 498 } 499 500 static void 501 usage(void) 502 { 503 if (mode == P_NEWALIASES) 504 fprintf(stderr, "usage: %s [-f file]\n", __progname); 505 else 506 fprintf(stderr, "usage: %s [-U] [-d dbtype] [-o dbfile] " 507 "[-t type] file\n", __progname); 508 exit(1); 509 } 510