1/*++
2/* NAME
3/*	mail_version 3
4/* SUMMARY
5/*	time-dependent probe sender addresses
6/* SYNOPSIS
7/*	#include <mail_version.h>
8/*
9/*	typedef struct {
10/*	    char   *program;		/* postfix */
11/*	    int     major;		/* 2 */
12/*	    int     minor;		/* 9 */
13/*	    int     patch;		/* patchlevel or -1 */
14/*	    char   *snapshot;		/* null or snapshot info */
15/*	} MAIL_VERSION;
16/*
17/*	MAIL_VERSION *mail_version_parse(version_string, why)
18/*	const char *version_string;
19/*	const char **why;
20/*
21/*	void	mail_version_free(mp)
22/*	MAIL_VERSION *mp;
23/*
24/*	const char *get_mail_version()
25/*
26/*	int	check_mail_version(version_string)
27/*	const char *version_string;
28/* DESCRIPTION
29/*	This module understands the format of Postfix version strings
30/*	(for example the default value of "mail_version"), and
31/*	provides support to compare the compile-time version of a
32/*	Postfix program with the run-time version of a Postfix
33/*	library. Apparently, some distributions don't use proper
34/*	so-number versioning, causing programs to fail erratically
35/*	after an update replaces the library but not the program.
36/*
37/*	A Postfix version string consists of two or three parts
38/*	separated by a single "-" character:
39/* .IP \(bu
40/*	The first part is a string with the program name.
41/* .IP \(bu
42/*	The second part is the program version: either two or three
43/*	non-negative integer numbers separated by single "."
44/*	character. Stable releases have a major version, minor
45/*	version and patchlevel; experimental releases (snapshots)
46/*	have only major and minor version numbers.
47/* .IP \(bu
48/*	The third part is ignored with a stable release, otherwise
49/*	it is a string with the snapshot release date plus some
50/*	optional information.
51/*
52/*	mail_version_parse() parses a version string.
53/*
54/*	get_mail_version() returns the version string (the value
55/*	of DEF_MAIL_VERSION) that is compiled into the library.
56/*
57/*	check_mail_version() compares the caller's version string
58/*	(usually the value of DEF_MAIL_VERSION) that is compiled
59/*	into the caller, and logs a warning when the strings differ.
60/* DIAGNOSTICS
61/*	In the case of a parsing error, mail_version_parse() returns
62/*	a null pointer, and sets the why argument to a string with
63/*	problem details.
64/* LICENSE
65/* .ad
66/* .fi
67/*	The Secure Mailer license must be distributed with this software.
68/* AUTHOR(S)
69/*	Wietse Venema
70/*	IBM T.J. Watson Research
71/*	P.O. Box 704
72/*	Yorktown Heights, NY 10598, USA
73/*--*/
74
75/* System library. */
76
77#include <sys_defs.h>
78#include <stdlib.h>
79#include <errno.h>
80
81/* Utility library. */
82
83#include <msg.h>
84#include <mymalloc.h>
85#include <stringops.h>
86#include <split_at.h>
87
88/* Global library. */
89
90#include <mail_version.h>
91
92/* mail_version_int - convert integer */
93
94static int mail_version_int(const char *strval)
95{
96    char   *end;
97    int     intval;
98    long    longval;
99
100    errno = 0;
101    intval = longval = strtol(strval, &end, 10);
102    if (*strval == 0 || *end != 0 || errno == ERANGE || longval != intval)
103	intval = (-1);
104    return (intval);
105}
106
107/* mail_version_worker - do the parsing work */
108
109static const char *mail_version_worker(MAIL_VERSION *mp, char *cp)
110{
111    char   *major_field;
112    char   *minor_field;
113    char   *patch_field;
114
115    /*
116     * Program name.
117     */
118    if ((mp->program = mystrtok(&cp, "-")) == 0)
119	return ("no program name");
120
121    /*
122     * Major, minor, patchlevel. If this is a stable release, then we ignore
123     * text after the patchlevel, in case there are vendor extensions.
124     */
125    if ((major_field = mystrtok(&cp, "-")) == 0)
126	return ("missing major version");
127
128    if ((minor_field = split_at(major_field, '.')) == 0)
129	return ("missing minor version");
130    if ((mp->major = mail_version_int(major_field)) < 0)
131	return ("bad major version");
132    patch_field = split_at(minor_field, '.');
133    if ((mp->minor = mail_version_int(minor_field)) < 0)
134	return ("bad minor version");
135
136    if (patch_field == 0)
137	mp->patch = -1;
138    else if ((mp->patch = mail_version_int(patch_field)) < 0)
139	return ("bad patchlevel");
140
141    /*
142     * Experimental release. If this is not a stable release, we take
143     * everything to the end of the string.
144     */
145    if (patch_field != 0)
146	mp->snapshot = 0;
147    else if ((mp->snapshot = mystrtok(&cp, "")) == 0)
148	return ("missing snapshot field");
149
150    return (0);
151}
152
153/* mail_version_parse - driver */
154
155MAIL_VERSION *mail_version_parse(const char *string, const char **why)
156{
157    MAIL_VERSION *mp;
158    char   *saved_string;
159    const char *err;
160
161    mp = (MAIL_VERSION *) mymalloc(sizeof(*mp));
162    saved_string = mystrdup(string);
163    if ((err = mail_version_worker(mp, saved_string)) != 0) {
164	*why = err;
165	myfree(saved_string);
166	myfree((char *) mp);
167	return (0);
168    } else {
169	return (mp);
170    }
171}
172
173/* mail_version_free - destroy version information */
174
175void    mail_version_free(MAIL_VERSION *mp)
176{
177    myfree(mp->program);
178    myfree((char *) mp);
179}
180
181/* get_mail_version - return parsed mail version string */
182
183const char *get_mail_version(void)
184{
185    return (DEF_MAIL_VERSION);
186}
187
188/* check_mail_version - compare caller version with library version */
189
190void    check_mail_version(const char *version_string)
191{
192    if (strcmp(version_string, DEF_MAIL_VERSION) != 0)
193	msg_warn("Postfix library version mis-match: wanted %s, found %s",
194		 version_string, DEF_MAIL_VERSION);
195}
196
197#ifdef TEST
198
199#include <unistd.h>
200#include <vstring.h>
201#include <vstream.h>
202#include <vstring_vstream.h>
203
204#define STR(x) vstring_str(x)
205
206/* parse_sample - parse a sample string from argv or stdin */
207
208static void parse_sample(const char *sample)
209{
210    MAIL_VERSION *mp;
211    const char *why;
212
213    mp = mail_version_parse(sample, &why);
214    if (mp == 0) {
215	vstream_printf("ERROR: %s: %s\n", sample, why);
216    } else {
217	vstream_printf("program: %s\t", mp->program);
218	vstream_printf("major: %d\t", mp->major);
219	vstream_printf("minor: %d\t", mp->minor);
220	if (mp->patch < 0)
221	    vstream_printf("snapshot: %s\n", mp->snapshot);
222	else
223	    vstream_printf("patch: %d\n", mp->patch);
224	mail_version_free(mp);
225    }
226    vstream_fflush(VSTREAM_OUT);
227}
228
229/* main - the main program */
230
231int     main(int argc, char **argv)
232{
233    VSTRING *inbuf = vstring_alloc(1);
234    int     have_tty = isatty(0);
235
236    if (argc > 1) {
237	while (--argc > 0 && *++argv)
238	    parse_sample(*argv);
239    } else {
240	for (;;) {
241	    if (have_tty) {
242		vstream_printf("> ");
243		vstream_fflush(VSTREAM_OUT);
244	    }
245	    if (vstring_fgets_nonl(inbuf, VSTREAM_IN) <= 0)
246		break;
247	    if (have_tty == 0)
248		vstream_printf("> %s\n", STR(inbuf));
249	    if (*STR(inbuf) == 0 || *STR(inbuf) == '#')
250		continue;
251	    parse_sample(STR(inbuf));
252	}
253    }
254    vstring_free(inbuf);
255    return (0);
256}
257
258#endif
259