1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
9 /* A small freestanding program to build dbm databases from serial input. For
10 alias files, this program fulfils the function of the newaliases program used
11 by other mailers, but it can be used for other dbm data files too. It operates
12 by writing a new file or files, and then renaming; otherwise old entries can
13 never get flushed out.
15 This program is clever enough to cope with ndbm, which creates two files called
16 <name>.dir and <name>.pag, or with db, which creates a single file called
17 <name>.db. If native db is in use (USE_DB defined) or tdb is in use (USE_TDB
18 defined) there is no extension to the output filename. This is also handled. If
19 there are any other variants, the program won't cope.
21 The first argument to the program is the name of the serial file; the second
22 is the base name for the DBM file(s). When native db is in use, these must be
25 Input lines beginning with # are ignored, as are blank lines. Entries begin
26 with a key terminated by a colon or end of line or whitespace and continue with
27 indented lines. Keys may be quoted if they contain colons or whitespace or #
33 uschar
* spool_directory
= NULL
; /* dummy for dbstuff.h */
35 /******************************************************************************/
36 /* dummies needed by Solaris build */
38 store_get_3(int size
, BOOL tainted
, const char *filename
, int linenumber
)
41 store_reset_3(void **ptr
, int pool
, const char *filename
, int linenumber
)
44 store_release_above_3(void *ptr
, const char *func
, int linenumber
)
47 string_vformat_trc(gstring
* g
, const uschar
* func
, unsigned line
,
48 unsigned size_limit
, unsigned flags
, const char *format
, va_list ap
)
50 /******************************************************************************/
53 #define max_insize 20000
54 #define max_outsize 100000
56 /* This is global because it's defined in the headers and compilers grumble
57 if it is made static. */
59 const uschar
*hex_digits
= CUS
"0123456789abcdef";
62 #ifdef STRERROR_FROM_ERRLIST
63 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
64 in their libraries, but can provide the same facility by this simple
65 alternative function. */
70 if (n
< 0 || n
>= sys_nerr
) return "unknown error number";
71 return sys_errlist
[n
];
73 #endif /* STRERROR_FROM_ERRLIST */
76 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
77 errors. This should help with debugging strange DB problems, e.g. getting "File
78 exists" when you try to open a db file. The API changed at release 4.3. */
80 #if defined(USE_DB) && defined(DB_VERSION_STRING)
82 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
83 dbfn_bdb_error_callback(const DB_ENV
*dbenv
, const char *pfx
, const char *msg
)
87 dbfn_bdb_error_callback(const char *pfx
, char *msg
)
91 printf("Berkeley DB error: %s\n", msg
);
97 /*************************************************
98 * Interpret escape sequence *
99 *************************************************/
101 /* This function is copied from the main Exim code.
104 pp points a pointer to the initiating "\" in the string;
105 the pointer gets updated to point to the final character
106 Returns: the value of the character escape
110 string_interpret_escape(const uschar
**pp
)
113 const uschar
*p
= *pp
;
115 if (isdigit(ch
) && ch
!= '8' && ch
!= '9')
118 if (isdigit(p
[1]) && p
[1] != '8' && p
[1] != '9')
120 ch
= ch
* 8 + *(++p
) - '0';
121 if (isdigit(p
[1]) && p
[1] != '8' && p
[1] != '9')
122 ch
= ch
* 8 + *(++p
) - '0';
127 case 'n': ch
= '\n'; break;
128 case 'r': ch
= '\r'; break;
129 case 't': ch
= '\t'; break;
135 Ustrchr(hex_digits
, tolower(*(++p
))) - hex_digits
;
136 if (isxdigit(p
[1])) ch
= ch
* 16 +
137 Ustrchr(hex_digits
, tolower(*(++p
))) - hex_digits
;
146 /*************************************************
148 *************************************************/
150 int main(int argc
, char **argv
)
158 BOOL lowercase
= TRUE
;
161 BOOL lastdup
= FALSE
;
162 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
168 EXIM_DATUM key
, content
;
170 uschar keybuffer
[256];
171 uschar temp_dbmname
[512];
172 uschar real_dbmname
[512];
174 uschar
*buffer
= malloc(max_outsize
);
175 uschar
*line
= malloc(max_insize
);
179 if (Ustrcmp(argv
[arg
], "-nolc") == 0) lowercase
= FALSE
;
180 else if (Ustrcmp(argv
[arg
], "-nowarn") == 0) warn
= FALSE
;
181 else if (Ustrcmp(argv
[arg
], "-lastdup") == 0) lastdup
= TRUE
;
182 else if (Ustrcmp(argv
[arg
], "-noduperr") == 0) duperr
= FALSE
;
183 else if (Ustrcmp(argv
[arg
], "-nozero") == 0) add_zero
= 0;
191 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
195 if (Ustrcmp(argv
[arg
], "-") == 0) f
= stdin
; else
197 f
= fopen(argv
[arg
], "rb");
200 printf("exim_dbmbuild: unable to open %s: %s\n", argv
[arg
], strerror(errno
));
205 /* By default Berkeley db does not put extensions on... which
208 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
209 if (Ustrcmp(argv
[arg
], argv
[arg
+1]) == 0)
211 printf("exim_dbmbuild: input and output filenames are the same\n");
216 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
219 if (strlen(argv
[arg
+1]) > sizeof(temp_dbmname
) - 20)
221 printf("exim_dbmbuild: output filename is ridiculously long\n");
225 Ustrcpy(temp_dbmname
, US argv
[arg
+1]);
226 Ustrcat(temp_dbmname
, US
".dbmbuild_temp");
228 Ustrcpy(dirname
, temp_dbmname
);
229 if ((bptr
= Ustrrchr(dirname
, '/')))
232 Ustrcpy(dirname
, US
".");
234 /* It is apparently necessary to open with O_RDWR for this to work
235 with gdbm-1.7.3, though no reading is actually going to be done. */
237 EXIM_DBOPEN(temp_dbmname
, dirname
, O_RDWR
|O_CREAT
|O_EXCL
, 0644, &d
);
241 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname
,
247 /* Unless using native db calls, see if we have created <name>.db; if not,
248 assume .dir & .pag */
250 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
251 sprintf(CS real_dbmname
, "%s.db", temp_dbmname
);
252 is_db
= Ustat(real_dbmname
, &statbuf
) == 0;
255 /* Now do the business */
260 while (Ufgets(line
, max_insize
, f
) != NULL
)
263 int len
= Ustrlen(line
);
267 if (len
>= max_insize
- 1 && p
[-1] != '\n')
269 printf("Overlong line read: max permitted length is %d\n", max_insize
- 1);
274 if (line
[0] == '#') continue;
275 while (p
> line
&& isspace(p
[-1])) p
--;
277 if (line
[0] == 0) continue;
279 /* A continuation line is valid only if there was a previous first
282 if (isspace(line
[0]))
287 printf("Unexpected continuation line ignored\n%s\n\n", line
);
290 while (isspace(*s
)) s
++;
293 if (bptr
- buffer
+ p
- s
>= max_outsize
- 1)
295 printf("Continued set of lines is too long: max permitted length is %d\n",
305 /* A first line must have a name followed by a colon or whitespace or
306 end of line, but first finish with a previous line. The key is lower
307 cased by default - this is what the newaliases program for sendmail does.
308 However, there's an option not to do this. */
318 EXIM_DATUM_INIT(content
);
319 EXIM_DATUM_DATA(content
) = CS buffer
;
320 EXIM_DATUM_SIZE(content
) = bptr
- buffer
+ add_zero
;
322 switch(rc
= EXIM_DBPUTB(d
, key
, content
))
328 case EXIM_DBPUTB_DUP
:
329 if (warn
) fprintf(stderr
, "** Duplicate key \"%s\"\n", keybuffer
);
331 if(duperr
) yield
= 1;
332 if (lastdup
) EXIM_DBPUT(d
, key
, content
);
336 fprintf(stderr
, "Error %d while writing key %s: errno=%d\n", rc
,
345 EXIM_DATUM_INIT(key
);
346 EXIM_DATUM_DATA(key
) = CS keybuffer
;
348 /* Deal with quoted keys. Escape sequences always make one character
349 out of several, so we can re-build in place. */
355 while (*s
!= 0 && *s
!= '\"')
358 ? string_interpret_escape((const uschar
**)&s
)
362 if (*s
!= 0) s
++; /* Past terminating " */
363 EXIM_DATUM_SIZE(key
) = t
- keystart
+ add_zero
;
368 while (*s
!= 0 && *s
!= ':' && !isspace(*s
)) s
++;
369 EXIM_DATUM_SIZE(key
) = s
- keystart
+ add_zero
;
372 if (EXIM_DATUM_SIZE(key
) > 256)
374 printf("Keys longer than 255 characters cannot be handled\n");
381 for (i
= 0; i
< EXIM_DATUM_SIZE(key
) - add_zero
; i
++)
382 keybuffer
[i
] = tolower(keystart
[i
]);
384 for (i
= 0; i
< EXIM_DATUM_SIZE(key
) - add_zero
; i
++)
385 keybuffer
[i
] = keystart
[i
];
390 while (isspace(*s
))s
++;
394 while (isspace(*s
))s
++;
408 EXIM_DATUM_INIT(content
);
409 EXIM_DATUM_DATA(content
) = CS buffer
;
410 EXIM_DATUM_SIZE(content
) = bptr
- buffer
+ add_zero
;
412 switch(rc
= EXIM_DBPUTB(d
, key
, content
))
418 case EXIM_DBPUTB_DUP
:
419 if (warn
) fprintf(stderr
, "** Duplicate key \"%s\"\n", keybuffer
);
421 if (duperr
) yield
= 1;
422 if (lastdup
) EXIM_DBPUT(d
, key
, content
);
426 fprintf(stderr
, "Error %d while writing key %s: errno=%d\n", rc
,
433 /* Close files, rename or abandon the temporary files, and exit */
440 /* If successful, output the number of entries and rename the temporary
443 if (yield
== 0 || yield
== 1)
445 printf("%d entr%s written\n", count
, (count
== 1)? "y" : "ies");
448 printf("%d duplicate key%s \n", dupcount
, (dupcount
> 1)? "s" : "");
451 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
452 Ustrcpy(real_dbmname
, temp_dbmname
);
453 Ustrcpy(buffer
, US argv
[arg
+1]);
454 if (Urename(real_dbmname
, buffer
) != 0)
456 printf("Unable to rename %s as %s\n", real_dbmname
, buffer
);
461 /* Rename a single .db file */
465 sprintf(CS real_dbmname
, "%s.db", temp_dbmname
);
466 sprintf(CS buffer
, "%s.db", argv
[arg
+1]);
467 if (Urename(real_dbmname
, buffer
) != 0)
469 printf("Unable to rename %s as %s\n", real_dbmname
, buffer
);
474 /* Rename .dir and .pag files */
478 sprintf(CS real_dbmname
, "%s.dir", temp_dbmname
);
479 sprintf(CS buffer
, "%s.dir", argv
[arg
+1]);
480 if (Urename(real_dbmname
, buffer
) != 0)
482 printf("Unable to rename %s as %s\n", real_dbmname
, buffer
);
486 sprintf(CS real_dbmname
, "%s.pag", temp_dbmname
);
487 sprintf(CS buffer
, "%s.pag", argv
[arg
+1]);
488 if (Urename(real_dbmname
, buffer
) != 0)
490 printf("Unable to rename %s as %s\n", real_dbmname
, buffer
);
495 #endif /* USE_DB || USE_TDB || USE_GDBM */
498 /* Otherwise unlink the temporary files. */
502 printf("dbmbuild abandoned\n");
503 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
504 /* We created it, so safe to delete despite the name coming from outside */
505 /* coverity[tainted_string] */
506 Uunlink(temp_dbmname
);
510 sprintf(CS real_dbmname
, "%s.db", temp_dbmname
);
511 Uunlink(real_dbmname
);
515 sprintf(CS real_dbmname
, "%s.dir", temp_dbmname
);
516 Uunlink(real_dbmname
);
517 sprintf(CS real_dbmname
, "%s.pag", temp_dbmname
);
518 Uunlink(real_dbmname
);
520 #endif /* USE_DB || USE_TDB */
526 /* End of exim_dbmbuild.c */