xref: /plan9/sys/src/cmd/auth/secstore/secstored.c (revision e06f534bbaa4097bc6f4764ef1dd2dc3338fbd40)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <ndb.h>
5 #include <mp.h>
6 #include <libsec.h>
7 #include "SConn.h"
8 #include "secstore.h"
9 
10 char* secureidcheck(char *, char *);   // from /sys/src/cmd/auth/
11 extern char* dirls(char *path);
12 
13 int verbose;
14 Ndb *db;
15 
16 static void
17 usage(void)
18 {
19 	fprint(2, "usage: secstored [-R] [-S servername] [-s tcp!*!5356] [-v] [-x netmtpt]\n");
20 	exits("usage");
21 }
22 
23 static int
24 getdir(SConn *conn, char *id)
25 {
26 	char *ls, *s;
27 	uchar *msg;
28 	int n, len;
29 
30 	s = emalloc(Maxmsg);
31 	snprint(s, Maxmsg, "%s/store/%s", SECSTORE_DIR, id);
32 
33 	if((ls = dirls(s)) == nil)
34 		len = 0;
35 	else
36 		len = strlen(ls);
37 
38 	/* send file size */
39 	snprint(s, Maxmsg, "%d", len);
40 	conn->write(conn, (uchar*)s, strlen(s));
41 
42 	/* send directory listing in Maxmsg chunks */
43 	n = Maxmsg;
44 	msg = (uchar*)ls;
45 	while(len > 0){
46 		if(len < Maxmsg)
47 			n = len;
48 		conn->write(conn, msg, n);
49 		msg += n;
50 		len -= n;
51 	}
52 	free(s);
53 	free(ls);
54 	return 0;
55 }
56 
57 static int
58 getfile(SConn *conn, char *id, char *gf)
59 {
60 	int n, gd, len;
61 	ulong mode;
62 	char *s;
63 	Dir *st;
64 
65 	if(strcmp(gf,".")==0)
66 		return getdir(conn, id);
67 
68 	/* send file size */
69 	s = emalloc(Maxmsg);
70 	snprint(s, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, gf);
71 	gd = open(s, OREAD);
72 	if(gd < 0){
73 		syslog(0, LOG, "can't open %s: %r\n", s);
74 		free(s);
75 		conn->write(conn, (uchar*)"-1", 2);
76 		return -1;
77 	}
78 	st = dirfstat(gd);
79 	if(st == nil){
80 		syslog(0, LOG, "can't stat %s: %r\n", s);
81 		free(s);
82 		conn->write(conn, (uchar*)"-1", 2);
83 		return -1;
84 	}
85 	mode = st->mode;
86 	len = st->length;
87 	free(st);
88 	if(mode & DMDIR) {
89 		syslog(0, LOG, "%s should be a plain file, not a directory\n", s);
90 		free(s);
91 		conn->write(conn, (uchar*)"-1", 2);
92 		return -1;
93 	}
94 	if(len < 0 || len > MAXFILESIZE){
95 		syslog(0, LOG, "implausible filesize %d for %s\n", len, gf);
96 		free(s);
97 		conn->write(conn, (uchar*)"-3", 2);
98 		return -1;
99 	}
100 	snprint(s, Maxmsg, "%d", len);
101 	conn->write(conn, (uchar*)s, strlen(s));
102 
103 	/* send file in Maxmsg chunks */
104 	while(len > 0){
105 		n = read(gd, s, Maxmsg);
106 		if(n <= 0){
107 			syslog(0, LOG, "read error on %s: %r\n", gf);
108 			free(s);
109 			return -1;
110 		}
111 		conn->write(conn, (uchar*)s, n);
112 		len -= n;
113 	}
114 	close(gd);
115 	free(s);
116 	return 0;
117 }
118 
119 static int
120 putfile(SConn *conn, char *id, char *pf)
121 {
122 	int n, nw, pd;
123 	long len;
124 	char s[Maxmsg+1];
125 
126 	/* get file size */
127 	n = readstr(conn, s);
128 	if(n < 0){
129 		syslog(0, LOG, "remote: %s: %r\n", s);
130 		return -1;
131 	}
132 	len = atoi(s);
133 	if(len == -1){
134 		syslog(0, LOG, "remote file %s does not exist\n", pf);
135 		return -1;
136 	}else if(len < 0 || len > MAXFILESIZE){
137 		syslog(0, LOG, "implausible filesize %ld for %s\n", len, pf);
138 		return -1;
139 	}
140 
141 	/* get file in Maxmsg chunks */
142 	if(strchr(pf,'/') != nil || strcmp(pf,"..")==0){
143 		syslog(0, LOG, "no slashes allowed: %s\n", pf);
144 		return -1;
145 	}
146 	snprint(s, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, pf);
147 	pd = create(s, OWRITE, 0660);
148 	if(pd < 0){
149 		syslog(0, LOG, "can't open %s: %r\n", s);
150 		return -1;
151 	}
152 	while(len > 0){
153 		n = conn->read(conn, (uchar*)s, Maxmsg);
154 		if(n <= 0){
155 			syslog(0, LOG, "empty file chunk\n");
156 			return -1;
157 		}
158 		nw = write(pd, s, n);
159 		if(nw != n){
160 			syslog(0, LOG, "write error on %s: %r", pf);
161 			return -1;
162 		}
163 		len -= n;
164 	}
165 	close(pd);
166 	return 0;
167 
168 }
169 
170 static int
171 removefile(SConn *conn, char *id, char *f)
172 {
173 	Dir *d;
174 	char buf[Maxmsg];
175 
176 	snprint(buf, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, f);
177 
178 	if((d = dirstat(buf)) == nil){
179 		snprint(buf, sizeof buf, "remove failed: %r");
180 		writerr(conn, buf);
181 		return -1;
182 	}else if(d->mode & DMDIR){
183 		snprint(buf, sizeof buf, "can't remove a directory");
184 		writerr(conn, buf);
185 		free(d);
186 		return -1;
187 	}
188 
189 	free(d);
190 	if(remove(buf) < 0){
191 		snprint(buf, sizeof buf, "remove failed: %r");
192 		writerr(conn, buf);
193 		return -1;
194 	}
195 	return 0;
196 }
197 
198 /* given line directory from accept, returns ipaddr!port */
199 static char*
200 remoteIP(char *ldir)
201 {
202 	int fd, n;
203 	char rp[100], ap[500];
204 
205 	snprint(rp, sizeof rp, "%s/remote", ldir);
206 	fd = open(rp, OREAD);
207 	if(fd < 0)
208 		return strdup("?!?");
209 	n = read(fd, ap, sizeof ap);
210 	if(n <= 0 || n == sizeof ap){
211 		fprint(2, "secstored: error %d reading %s: %r\n", n, rp);
212 		return strdup("?!?");
213 	}
214 	close(fd);
215 	ap[n--] = 0;
216 	if(ap[n] == '\n')
217 		ap[n] = 0;
218 	return strdup(ap);
219 }
220 
221 static int
222 dologin(int fd, char *S, int forceSTA)
223 {
224 	int i, n, rv;
225 	char *file, *mess;
226 	char msg[Maxmsg+1];
227 	PW *pw;
228 	SConn *conn;
229 
230 	pw = nil;
231 	rv = -1;
232 
233 	// collect the first message
234 	if((conn = newSConn(fd)) == nil)
235 		return -1;
236 	if(readstr(conn, msg) < 0){
237 		fprint(2, "secstored: remote: %s: %r\n", msg);
238 		writerr(conn, "can't read your first message");
239 		goto Out;
240 	}
241 
242 	// authenticate
243 	if(PAKserver(conn, S, msg, &pw) < 0){
244 		if(pw != nil)
245 			syslog(0, LOG, "secstore denied for %s", pw->id);
246 		goto Out;
247 	}
248 	if((forceSTA || pw->status&STA) != 0){
249 		conn->write(conn, (uchar*)"STA", 3);
250 		if(readstr(conn, msg) < 10 || strncmp(msg, "STA", 3) != 0){
251 			syslog(0, LOG, "no STA from %s", pw->id);
252 			goto Out;
253 		}
254 		mess = secureidcheck(pw->id, msg+3);
255 		if(mess != nil){
256 			syslog(0, LOG, "secureidcheck denied %s because %s", pw->id, mess);
257 			goto Out;
258 		}
259 	}
260 	conn->write(conn, (uchar*)"OK", 2);
261 	syslog(0, LOG, "AUTH %s", pw->id);
262 
263 	// perform operations as asked
264 	while((n = readstr(conn, msg)) > 0){
265 		syslog(0, LOG, "[%s] %s", pw->id, msg);
266 
267 		if(strncmp(msg, "GET ", 4) == 0){
268 			file = validatefile(msg+4);
269 			if(file==nil || getfile(conn, pw->id, file) < 0)
270 				goto Err;
271 
272 		}else if(strncmp(msg, "PUT ", 4) == 0){
273 			file = validatefile(msg+4);
274 			if(file==nil || putfile(conn, pw->id, file) < 0){
275 				syslog(0, LOG, "failed PUT %s/%s", pw->id, file);
276 				goto Err;
277 			}
278 
279 		}else if(strncmp(msg, "RM ", 3) == 0){
280 			file = validatefile(msg+3);
281 			if(file==nil || removefile(conn, pw->id, file) < 0){
282 				syslog(0, LOG, "failed RM %s/%s", pw->id, file);
283 				goto Err;
284 			}
285 
286 		}else if(strncmp(msg, "CHPASS", 6) == 0){
287 			if(readstr(conn, msg) < 0){
288 				syslog(0, LOG, "protocol botch CHPASS for %s", pw->id);
289 				writerr(conn, "protocol botch while setting PAK");
290 				goto Out;
291 			}
292 			pw->Hi = strtomp(msg, nil, 64, pw->Hi);
293 			for(i=0; i < 4 && putPW(pw) < 0; i++)
294 				syslog(0, LOG, "password change failed for %s (%d): %r", pw->id, i);
295 			if(i==4)
296 				goto Out;
297 
298 		}else if(strncmp(msg, "BYE", 3) == 0){
299 			rv = 0;
300 			break;
301 
302 		}else{
303 			writerr(conn, "unrecognized operation");
304 			break;
305 		}
306 
307 	}
308 	if(n <= 0)
309 		syslog(0, LOG, "%s closed connection without saying goodbye\n", pw->id);
310 
311 Out:
312 	freePW(pw);
313 	conn->free(conn);
314 	return rv;
315 Err:
316 	writerr(conn, "operation failed");
317 	goto Out;
318 }
319 
320 void
321 main(int argc, char **argv)
322 {
323 	int afd, dfd, lcfd, forceSTA = 0;
324 	char adir[40], ldir[40], *remote;
325 	char *serve = "tcp!*!5356", *p, aserve[128], net[128];
326 	char *S = "secstore";
327 	Ndb *db2;
328 
329 	setnetmtpt(net, sizeof(net), nil);
330 	ARGBEGIN{
331 	case 'R':
332 		forceSTA = 1;
333 		break;
334 	case 's':
335 		serve = EARGF(usage());
336 		break;
337 	case 'S':
338 		S = EARGF(usage());
339 		break;
340 	case 'x':
341 		p = ARGF();
342 		if(p == nil)
343 			usage();
344 		setnetmtpt(net, sizeof(net), p);
345 		break;
346 	case 'v':
347 		verbose++;
348 		break;
349 	default:
350 		usage();
351 	}ARGEND;
352 
353 	if(!verbose)
354 		switch(rfork(RFNOTEG|RFPROC|RFFDG)) {
355 		case -1:
356 			sysfatal("fork: %r");
357 		case 0:
358 			break;
359 		default:
360 			exits(0);
361 		}
362 
363 	snprint(aserve, sizeof aserve, "%s/%s", net, serve);
364 	afd = announce(aserve, adir);
365 	if(afd < 0)
366 		sysfatal("%s: %r\n", aserve);
367 	syslog(0, LOG, "ANNOUNCE %s", aserve);
368 	for(;;){
369 		if((lcfd = listen(adir, ldir)) < 0)
370 			exits("can't listen");
371 		switch(fork()){
372 		case -1:
373 			fprint(2, "secstore forking: %r\n");
374 			close(lcfd);
375 			break;
376 		case 0:
377 			// "/lib/ndb/common.radius does not exist" if db set before fork
378 			db = ndbopen("/lib/ndb/auth");
379 			if(db == 0)
380 				syslog(0, LOG, "no /lib/ndb/auth");
381 			db2 = ndbopen(0);
382 			if(db2 == 0)
383 				syslog(0, LOG, "no /lib/ndb/local");
384 			db = ndbcat(db, db2);
385 			if((dfd = accept(lcfd, ldir)) < 0)
386 				exits("can't accept");
387 			alarm(30*60*1000); 	// 30 min
388 			remote = remoteIP(ldir);
389 			syslog(0, LOG, "secstore from %s", remote);
390 			free(remote);
391 			dologin(dfd, S, forceSTA);
392 			exits(nil);
393 		default:
394 			close(lcfd);
395 			break;
396 		}
397 	}
398 }
399 
400