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