1 /*
2  * Copyright (c) 2013 Gilles Chehade <gilles@poolp.org>
3  * Copyright (c) 2016 Joerg Jung <jung@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include "includes.h"
19 
20 #include <sys/types.h>
21 
22 #include <err.h>
23 #include <pwd.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <limits.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include <smtpd-api.h>
31 
32 static char	       *config;
33 static struct dict     *passwd;
34 
35 static int
parse_passwd_entry(int service,struct passwd * pw,char * buf)36 parse_passwd_entry(int service, struct passwd *pw, char *buf)
37 {
38 	const char     *e;
39 	char	       *p;
40 
41 	/* username */
42 	if (!(pw->pw_name = strsep(&buf, ":")) || !strlen(pw->pw_name))
43 		return 0;
44 
45 	/* password */
46 	if (!(pw->pw_passwd = strsep(&buf, ":")) ||
47 	    (service == K_CREDENTIALS && !strlen(pw->pw_passwd)))
48 		return 0;
49 
50 	/* uid */
51 	if (!(p = strsep(&buf, ":")))
52 		return 0;
53 	pw->pw_uid = strtonum(p, 0, UID_MAX, &e);
54 	if (service == K_USERINFO && (!strlen(p) || e))
55 		return 0;
56 
57 	/* gid */
58 	if (!(p = strsep(&buf, ":")))
59 		return 0;
60 	pw->pw_gid = strtonum(p, 0, GID_MAX, &e);
61 	if (service == K_USERINFO && (!strlen(p) || e))
62 		return 0;
63 
64 	/* gecos */
65 	if (!(pw->pw_gecos = strsep(&buf, ":")))
66 		return 0;
67 
68 	/* home */
69 	if (!(pw->pw_dir = strsep(&buf, ":")) ||
70 	    (service == K_USERINFO && !strlen(pw->pw_dir)))
71 		return 0;
72 
73 	/* shell */
74 	pw->pw_shell = strsep(&buf, ":");
75 	/*
76 	 * explicitly allow further extra fields to support
77 	 * shared authentication with Dovecot Passwd-file format
78 	 */
79 	return 1;
80 }
81 
82 static int
table_passwd_update(void)83 table_passwd_update(void)
84 {
85 	FILE		*fp;
86 	char		*buf = NULL, tmp[LINE_MAX], *skip, *p;
87 	size_t		 sz = 0;
88 	ssize_t		 len;
89 	struct passwd	 pw;
90 	struct dict	*npasswd;
91 
92 	/* parse configuration */
93 	if ((fp = fopen(config, "r")) == NULL) {
94 		log_warn("warn: \"%s\"", config);
95 		return 0;
96 	}
97 
98 	if ((npasswd = calloc(1, sizeof(*passwd))) == NULL)
99 		goto err;
100 
101 	dict_init(npasswd);
102 
103 	while ((len = getline(&buf, &sz, fp)) != -1) {
104 		if (buf[len - 1] == '\n')
105 			buf[len - 1] = '\0';
106 
107 		/* skip commented entries */
108 		for (skip = buf; *skip; ++skip) {
109 			if (*skip == '#') {
110 				*skip = '\0';
111 				break;
112 			}
113 		}
114 		/* skip empty lines */
115 		if (strlen(buf) == 0)
116 			continue;
117 
118 		if (strlcpy(tmp, buf, sizeof(tmp)) >= sizeof(tmp)) {
119 			log_warnx("warn: line too long");
120 			goto err;
121 		}
122 
123 		if (!parse_passwd_entry(K_ANY, &pw, tmp)) {
124 			log_warnx("warn: invalid entry");
125 			goto err;
126 		}
127 		dict_set(npasswd, pw.pw_name, xstrdup(buf, "update"));
128 	}
129 	free(buf);
130 	fclose(fp);
131 
132 	/* swap passwd table and release old one*/
133 	if (passwd)
134 		while (dict_poproot(passwd, (void**)&p))
135 			free(p);
136 	passwd = npasswd;
137 
138 	return 1;
139 
140 err:
141 	free(buf);
142 	fclose(fp);
143 
144 	/* release passwd table */
145 	if (npasswd) {
146 		while (dict_poproot(npasswd, (void**)&p))
147 			free(p);
148 		free(npasswd);
149 	}
150 	return 0;
151 }
152 
153 static int
table_passwd_check(int service,struct dict * params,const char * key)154 table_passwd_check(int service, struct dict *params, const char *key)
155 {
156 	return -1;
157 }
158 
159 static int
table_passwd_lookup(int service,struct dict * params,const char * key,char * dst,size_t sz)160 table_passwd_lookup(int service, struct dict *params, const char *key,
161     char *dst, size_t sz)
162 {
163 	struct passwd	pw;
164 	char	       *line;
165 	char		tmp[LINE_MAX];
166 
167 	if ((line = dict_get(passwd, key)) == NULL)
168 		return 0;
169 
170 	(void)strlcpy(tmp, line, sizeof(tmp));
171 	if (!parse_passwd_entry(service, &pw, tmp)) {
172 		log_warnx("warn: invalid entry");
173 		return -1;
174 	}
175 
176 	switch (service) {
177 	case K_CREDENTIALS:
178 		if (snprintf(dst, sz, "%s:%s",
179 			pw.pw_name, pw.pw_passwd) >= (ssize_t)sz) {
180 			log_warnx("warn: result too large");
181 			return -1;
182 		}
183 		break;
184 	case K_USERINFO:
185 		if (snprintf(dst, sz, "%d:%d:%s", pw.pw_uid, pw.pw_gid,
186 		    pw.pw_dir) >= (ssize_t)sz) {
187 			log_warnx("warn: result too large");
188 			return -1;
189 		}
190 		break;
191 	default:
192 		log_warnx("warn: unknown service %d", service);
193 		return -1;
194 	}
195 	return 1;
196 }
197 
198 static int
table_passwd_fetch(int service,struct dict * params,char * dst,size_t sz)199 table_passwd_fetch(int service, struct dict *params, char *dst, size_t sz)
200 {
201 	return -1;
202 }
203 
204 int
main(int argc,char ** argv)205 main(int argc, char **argv)
206 {
207 	int ch;
208 
209 	log_init(1);
210 
211 	while ((ch = getopt(argc, argv, "")) != -1) {
212 		switch (ch) {
213 		default:
214 			fatalx("bad option");
215 			/* NOTREACHED */
216 		}
217 	}
218 	argc -= optind;
219 	argv += optind;
220 
221 	if (argc != 1)
222 		fatalx("bogus argument(s)");
223 
224 	config = argv[0];
225 
226 	if (table_passwd_update() == 0)
227 		fatalx("error parsing config file");
228 
229 	table_api_on_update(table_passwd_update);
230 	table_api_on_check(table_passwd_check);
231 	table_api_on_lookup(table_passwd_lookup);
232 	table_api_on_fetch(table_passwd_fetch);
233 	table_api_dispatch();
234 
235 	return 0;
236 }
237