1 /*
2  *
3  * CLEX File Manager
4  *
5  * Copyright (C) 2001-2018 Vlado Potisk <vlado_potisk@clex.sk>
6  *
7  * CLEX is free software without warranty of any kind; see the
8  * GNU General Public License as set out in the "COPYING" document
9  * which accompanies the CLEX File Manager package.
10  *
11  * CLEX can be downloaded from http://www.clex.sk
12  *
13  */
14 
15 /*
16  * Routines in userdata.c mostly provide access to data stored
17  * in /etc/passwd and /etc/group. All required data is read
18  * into memory to speed up lookups. The data returned by
19  * lookup_xxx() is valid only until next data refresh, the
20  * caller must copy the returned string value if necessary.
21  */
22 
23 #include "clexheaders.h"
24 
25 #include <sys/stat.h>		/* stat() */
26 #ifdef HAVE_UNAME
27 # include <sys/utsname.h>	/* uname() */
28 #endif
29 #include <time.h>			/* time() */
30 #include <grp.h>			/* getgrent() */
31 #include <pwd.h>			/* getpwent() */
32 #include <stdarg.h>			/* log.h */
33 #include <stdio.h>			/* sprintf() */
34 #include <stdlib.h>			/* qsort() */
35 #include <string.h>			/* strlen() */
36 #include <unistd.h>			/* stat() */
37 
38 #include "userdata.h"
39 
40 #include "edit.h"			/* edit_insertchar() */
41 #include "filter.h"			/* cx_filter() */
42 #include "log.h"			/* msgout() */
43 #include "match.h"			/* match_substr() */
44 #include "mbwstring.h"		/* convert2w() */
45 #include "util.h"			/* emalloc() */
46 
47 /*
48  * cached user(group) records are re-read when:
49  *  - /etc/passwd (/etc/group) file changes, or
50  *  - the cache expires (EXPIRATION in seconds) to allow
51  *    changes e.g. in NIS to get detected, or
52  *  - explicitly requested
53  */
54 #define EXPIRATION	300		/* 5 minutes */
55 
56 typedef struct pwdata {
57 	struct pwdata *next;	/* tmp pointer */
58 	SDSTRINGW login;
59 	SDSTRINGW homedir;
60 	SDSTRINGW gecos;
61 	uid_t uid;
62 } PWDATA;
63 
64 typedef struct grdata {
65 	struct grdata *next;	/* tmp pointer */
66 	SDSTRINGW group;
67 	gid_t gid;
68 } GRDATA;
69 
70 typedef struct {
71 	time_t timestamp;		/* when the data was obtained, or 0 */
72 	dev_t device;			/* device/inode for /etc/passwd */
73 	ino_t inode;
74 	int cnt;				/* # of entries */
75 	PWDATA **by_name;		/* sorted by name (for binary search, ignoring locale) */
76 	PWDATA **by_uid;		/* sorted by uid */
77 	PWDATA *ll;				/* linked list, unsorted */
78 } USERDATA;
79 
80 typedef struct {
81 	time_t timestamp;		/* when the data was obtained, or 0 */
82 	dev_t device;			/* device/inode for /etc/group */
83 	ino_t inode;
84 	int cnt;				/* # of entries */
85 	GRDATA **by_name;		/* sorted by name (for binary search, ignoring locale) */
86 	GRDATA **by_gid;		/* sorted by gid */
87 	GRDATA *ll;				/* linked list, unsorted */
88 } GROUPDATA;
89 
90 static USERDATA  utable;
91 static GROUPDATA gtable;
92 
93 static time_t now;
94 
95 static struct {
96 	const wchar_t *str;
97 	size_t len;
98 	int index;
99 } ufind, gfind;						/* used by user- and groupname_find() */
100 
101 static int
qcmp_name(const void * e1,const void * e2)102 qcmp_name(const void *e1, const void *e2)
103 {
104 	return wcscmp(	/* not wcscoll() */
105 	  SDSTR((*(PWDATA **)e1)->login),
106 	  SDSTR((*(PWDATA **)e2)->login));
107 }
108 
109 static int
qcmp_uid(const void * e1,const void * e2)110 qcmp_uid(const void *e1, const void *e2)
111 {
112 	return CMP((*(PWDATA **)e1)->uid,(*(PWDATA **)e2)->uid);
113 }
114 
115 static void
read_utable(void)116 read_utable(void)
117 {
118 	int i, cnt;
119 	PWDATA *ud = 0 /* prevent compiler warning */, *old;
120 	struct passwd *pw;
121 	static FLAG err = 0;
122 
123 	utable.timestamp = now;
124 	setpwent();
125 	for (old = utable.ll, cnt = 0; (pw = getpwent()); cnt++) {
126 		if (old) {
127 			/* use the old PWDATA struct */
128 			ud = old;
129 			old = ud->next;
130 		}
131 		else {
132 			/* create a new one */
133 			ud = emalloc(sizeof(PWDATA));
134 			ud->next = utable.ll;
135 			utable.ll = ud;
136 			SD_INIT(ud->login);
137 			SD_INIT(ud->homedir);
138 			SD_INIT(ud->gecos);
139 		}
140 		ud->uid = pw->pw_uid;
141 		sdw_copy(&ud->login,convert2w(pw->pw_name));
142 		sdw_copy(&ud->homedir,convert2w(pw->pw_dir));
143 		sdw_copy(&ud->gecos,convert2w(pw->pw_gecos));
144 	}
145 	endpwent();
146 
147 	/* free unused PWDATA structs */
148 	if (cnt > 0)
149 		for (; old; old = ud->next) {
150 			ud->next = old->next;
151 			sdw_reset(&old->login);
152 			sdw_reset(&old->homedir);
153 			sdw_reset(&old->gecos);
154 			free(old);
155 		}
156 
157 	if (utable.cnt != cnt && utable.by_name) {
158 		free(utable.by_name);
159 		free(utable.by_uid);
160 		utable.by_name = utable.by_uid = 0;
161 	}
162 
163 	/* I was told using errno for error detection with getpwent() is not portable */
164 	if ((utable.cnt = cnt) == 0) {
165 		utable.timestamp = 0;
166 		if (!TSET(err))
167 			msgout(MSG_W,"USER ACCOUNTS: Cannot obtain user account data");
168 		return;
169 	}
170 	if (TCLR(err))
171 		msgout(MSG_W,"USER ACCOUNTS: User account data is now available");
172 
173 	/* linked list -> two sorted arrays */
174 	if (utable.by_name == 0) {
175 		utable.by_name = emalloc(cnt * sizeof(PWDATA *));
176 		utable.by_uid  = emalloc(cnt * sizeof(PWDATA *));
177 		for (ud = utable.ll, i = 0; i < cnt; i++, ud = ud->next)
178 			utable.by_name[i] = utable.by_uid[i] = ud;
179 	}
180 	qsort(utable.by_name,cnt,sizeof(PWDATA *),qcmp_name);
181 	qsort(utable.by_uid ,cnt,sizeof(PWDATA *),qcmp_uid);
182 }
183 
184 static int
qcmp_gname(const void * e1,const void * e2)185 qcmp_gname(const void *e1, const void *e2)
186 {
187 	return wcscmp(	/* not wcscoll() */
188 	  SDSTR((*(GRDATA **)e1)->group),
189 	  SDSTR((*(GRDATA **)e2)->group));
190 }
191 
192 static int
qcmp_gid(const void * e1,const void * e2)193 qcmp_gid(const void *e1, const void *e2)
194 {
195 	return CMP((*(GRDATA **)e1)->gid,(*(GRDATA **)e2)->gid);
196 }
197 
198 static void
read_gtable(void)199 read_gtable(void)
200 {
201 	int i, cnt;
202 	GRDATA *gd = 0 /* prevent compiler warning */, *old;
203 	struct group *gr;
204 	static FLAG err = 0;
205 
206 	gtable.timestamp = now;
207 	setgrent();
208 	for (old = gtable.ll, cnt = 0; (gr = getgrent()); cnt++) {
209 		if (old) {
210 			/* use the old GRDATA struct */
211 			gd = old;
212 			old = gd->next;
213 		}
214 		else {
215 			/* create a new one */
216 			gd = emalloc(sizeof(GRDATA));
217 			gd->next = gtable.ll;
218 			gtable.ll = gd;
219 			SD_INIT(gd->group);
220 		}
221 		gd->gid = gr->gr_gid;
222 		sdw_copy(&gd->group,convert2w(gr->gr_name));
223 	}
224 	endgrent();
225 
226 	/* free unused GRDATA structs */
227 	if (cnt > 0)
228 		for (; old; old = gd->next) {
229 			gd->next = old->next;
230 			sdw_reset(&old->group);
231 			free(old);
232 		}
233 
234 	if (gtable.cnt != cnt && gtable.by_name) {
235 		free(gtable.by_name);
236 		free(gtable.by_gid);
237 		gtable.by_name = gtable.by_gid = 0;
238 	}
239 
240 	if ((gtable.cnt = cnt) == 0) {
241 		gtable.timestamp = 0;
242 		if (!TSET(err))
243 			msgout(MSG_W,"USER ACCOUNTS: Cannot obtain user group data");
244 		return;
245 	}
246 	if (TCLR(err))
247 		msgout(MSG_W,"USER ACCOUNTS: User group data is now available");
248 
249 	/* linked list -> sorted array */
250 	if (gtable.by_name == 0) {
251 		gtable.by_name = emalloc(cnt * sizeof(GRDATA *));
252 		gtable.by_gid  = emalloc(cnt * sizeof(GRDATA *));
253 		for (gd = gtable.ll, i = 0; i < cnt; i++, gd = gd->next)
254 			gtable.by_name[i] = gtable.by_gid[i] = gd;
255 	}
256 	qsort(gtable.by_name,cnt,sizeof(GRDATA *),qcmp_gname);
257 	qsort(gtable.by_gid ,cnt,sizeof(GRDATA *),qcmp_gid);
258 }
259 
260 static int
shelltype(const char * shell)261 shelltype(const char *shell)
262 {
263 	size_t len;
264 
265 	len = strlen(shell);
266 	if (len >= 2 && shell[len - 2] == 's' && shell[len - 1] == 'h')
267 		return (len >= 3 && shell[len - 3] == 'c') ? SHELL_CSH : SHELL_SH;
268 	return SHELL_OTHER;
269 }
270 
271 void
userdata_initialize(void)272 userdata_initialize(void)
273 {
274 	static char uidstr[24];
275 	static SDSTRING host = SDNULL("localhost");
276 	struct passwd *pw;
277 	const char *name, *xdg;
278 	uid_t myuid;
279 
280 #ifdef HAVE_UNAME
281 	char ch, *pch, *pdot;
282 	FLAG ip;
283 	struct utsname ut;
284 
285 	uname(&ut);
286 	sd_copy(&host,ut.nodename);
287 
288 	/* strip the domain part */
289 	for (ip = 1, pdot = 0, pch = SDSTR(host); (ch = *pch); pch++) {
290 		if (ch == '.') {
291 			if (pdot == 0)
292 				pdot = pch;
293 		}
294 		else if (ch < '0' || ch > '9')
295 			ip = 0;	/* this is a name and not an IP address */
296 		if (!ip && pdot != 0) {
297 			*pdot = '\0';
298 			break;
299 		}
300 	}
301 #endif
302 	user_data.host  = SDSTR(host);
303 	user_data.hostw = ewcsdup(convert2w(user_data.host));
304 
305 	user_data.nowrite = 0;
306 
307 	msgout(MSG_AUDIT,"CLEX version: \""VERSION "\"");
308 	msgout(MSG_HEADING,"Examining data of your account");
309 
310 	myuid = getuid();
311 	if ((pw = getpwuid(myuid)) == 0) {
312 		sprintf(uidstr,"%d",(int)myuid);
313 		msgout(MSG_W,"Cannot find your account (UID=%s)"
314 		  " in the user database",uidstr);
315 		sprintf(uidstr,"UID_%d",(int)myuid);
316 		user_data.login = uidstr;
317 		user_data.nowrite = 1;
318 	}
319 	else
320 		user_data.login = estrdup(pw->pw_name);
321 	user_data.loginw = ewcsdup(convert2w(user_data.login));
322 
323 	if (checkabs(name = getenv("SHELL")))
324 		user_data.shell = name;
325 	else if (pw && checkabs(pw->pw_shell))
326 		user_data.shell = estrdup(pw->pw_shell);
327 	else {
328 		msgout(MSG_W,"Cannot obtain the name of your shell program; using \"/bin/sh\"");
329 		user_data.shell = "/bin/sh";
330 	}
331 	name = base_name(user_data.shell);
332 	user_data.shellw = ewcsdup(convert2w(name));
333 	user_data.shelltype = shelltype(name);
334 	msgout(MSG_AUDIT,"Command interpreter: \"%s\"",user_data.shell);
335 
336 	if (checkabs(name = getenv("HOME"))) {
337 		user_data.homedir = name;
338 		if (strcmp(name,"/") == 0) {
339 			if (pw && *pw->pw_dir && strcmp(pw->pw_dir,"/") != 0) {
340 				msgout(MSG_W,"Your home directory is the root directory, "
341 				  "but according to the password file it should be \"%s\"",pw->pw_dir);
342 				user_data.nowrite = 1;
343 			}
344 		}
345 	}
346 	else if (pw && checkabs(pw->pw_dir))
347 		user_data.homedir = estrdup(pw->pw_dir);
348 	else {
349 		msgout(MSG_W,"Cannot obtain the name of your home directory; using \"/\"");
350 		user_data.homedir = "/";
351 		user_data.nowrite = 1;
352 	}
353 
354 	if (!user_data.nowrite && strcmp(user_data.homedir,"/") == 0 && myuid != 0) {
355 		msgout(MSG_W,"Your home directory is the root directory, but you are not root");
356 		user_data.nowrite = 1;
357 	}
358 
359 	user_data.homedirw = ewcsdup(convert2w(user_data.homedir));
360 	msgout(MSG_DEBUG,"Home directory: \"%s\"",user_data.homedir);
361 
362 	if (user_data.nowrite)
363 		msgout(MSG_W,"Due to the problem reported above CLEX will not save any "
364 		  "data to disk. This includes configuration, options and bookmarks");
365 
366 	user_data.isroot = geteuid() == 0;  /* 0 or 1 */;
367 
368 	xdg = getenv("XDG_CONFIG_HOME");
369 	if (xdg && *xdg) {
370 		pathname_set_directory(xdg);
371 		user_data.subdir = estrdup(pathname_join("clex"));
372 	}
373 	else {
374 		pathname_set_directory(user_data.homedir);
375 		user_data.subdir = estrdup(pathname_join(".config/clex"));
376 	}
377 	msgout(MSG_DEBUG,"Configuration directory: \"%s\"",user_data.subdir);
378 	pathname_set_directory(user_data.subdir);
379 	user_data.file_cfg = estrdup(pathname_join("config"));
380 	user_data.file_opt = estrdup(pathname_join("options"));
381 	user_data.file_bm  = estrdup(pathname_join("bookmarks"));
382 
383 	msgout(MSG_HEADING,0);
384 }
385 
386 void
userdata_expire(void)387 userdata_expire(void)
388 {
389 	 utable.timestamp = gtable.timestamp = 0;
390 }
391 
392 /* returns 1 if data was re-read, 0 if unchanged */
393 int
userdata_refresh(void)394 userdata_refresh(void)
395 {
396 	FLAG stat_ok;
397 	int reloaded;
398 	struct stat st;
399 
400 	reloaded = 0;
401 
402 	now = time(0);
403 	stat_ok = stat("/etc/passwd",&st) == 0;
404 	if (!stat_ok || st.st_mtime >= utable.timestamp
405 	  || st.st_dev != utable.device || st.st_ino != utable.inode
406 	  || now > utable.timestamp + EXPIRATION ) {
407 		read_utable();
408 		utable.device = stat_ok ? st.st_dev : 0;
409 		utable.inode  = stat_ok ? st.st_ino : 0;
410 		reloaded = 1;
411 	}
412 
413 	stat_ok = stat("/etc/group",&st) == 0;
414 	if (!stat_ok || st.st_mtime >= gtable.timestamp
415 	  || st.st_dev != gtable.device || st.st_ino != gtable.inode
416       || now > gtable.timestamp + EXPIRATION) {
417 		read_gtable();
418 		gtable.device = stat_ok ? st.st_dev : 0;
419 		gtable.inode  = stat_ok ? st.st_ino : 0;
420 		reloaded = 1;
421 	}
422 
423 	return reloaded;
424 }
425 
426 /* simple binary search algorithm */
427 #define BIN_SEARCH(COUNT,CMPFUNC,RETVAL) \
428 	{ \
429 		int min, med, max, cmp; \
430 		for (min = 0, max = COUNT - 1; min <= max; ) { \
431 			med = (min + max) / 2; \
432 			cmp = CMPFUNC; \
433 			if (cmp == 0) \
434 				return RETVAL; \
435 			if (cmp < 0) \
436 				max = med - 1; \
437 			else \
438 				min = med + 1; \
439 		} \
440 		return 0; \
441 	}
442 /* end of BIN_SEARCH() macro */
443 
444 /* numeric uid -> login name */
445 const wchar_t *
lookup_login(uid_t uid)446 lookup_login(uid_t uid)
447 {
448 	BIN_SEARCH(utable.cnt,
449 	  CMP(uid,utable.by_uid[med]->uid),
450 	  SDSTR(utable.by_uid[med]->login))
451 }
452 
453 /* numeric gid -> group name */
454 const wchar_t *
lookup_group(gid_t gid)455 lookup_group(gid_t gid)
456 {
457 	BIN_SEARCH(gtable.cnt,
458 	  CMP(gid,gtable.by_gid[med]->gid),
459 	  SDSTR(gtable.by_gid[med]->group))
460 }
461 
462 static const wchar_t *
lookup_homedir(const wchar_t * user,size_t len)463 lookup_homedir(const wchar_t *user, size_t len)
464 {
465 	static SDSTRINGW username = SDNULL(L"");
466 
467 	if (len == 0)
468 		return user_data.homedirw;
469 
470 	sdw_copyn(&username,user,len);
471 	BIN_SEARCH(utable.cnt,
472 	  wcscmp(SDSTR(username),SDSTR(utable.by_name[med]->login)),
473 	  SDSTR(utable.by_name[med]->homedir))
474 }
475 
476 /*
477  * check if 'dir' is of the form ~username/dir with a valid username
478  * typical usage:
479  *   tilde = is_dir_tilde(dir);
480  *   ... dequote dir ...
481  *   if (tilde) dir = dir_tilde(dir);
482  * note: without dequoting is this sufficient:
483  *   tilde = *dir == '~';
484  */
485 int
is_dir_tilde(const wchar_t * dir)486 is_dir_tilde(const wchar_t *dir)
487 {
488 	size_t i;
489 
490 	if (*dir != L'~')
491 		return 0;
492 
493 	for (i = 1; dir[i] != L'\0' && dir[i] != L'/'; i++)
494 		;
495 	return lookup_homedir(dir + 1,i - 1) != 0;
496 }
497 
498 /*
499  * dir_tilde() function performs tilde substitution. It understands
500  * ~user/dir notation and transforms it to proper directory name.
501  * The result of the substitution (if performed) is stored in
502  * a static buffer that might get overwritten by successive calls.
503  */
504 const wchar_t *
dir_tilde(const wchar_t * dir)505 dir_tilde(const wchar_t *dir)
506 {
507 	size_t i;
508 	const wchar_t *home;
509 	static USTRINGW buff = UNULL;
510 
511 	if (*dir != L'~')
512 		return dir;
513 
514 	for (i = 1; dir[i] != L'\0' && dir[i] != L'/'; i++)
515 		;
516 	home = lookup_homedir(dir + 1,i - 1);
517 	if (home == 0)
518 		return dir;		/* no such user */
519 
520 	usw_cat(&buff,home,dir + i,(wchar_t *)0);
521 	return USTR(buff);
522 }
523 
524 /*
525  * Following two functions implement username completion. First
526  * username_find_init() is called to initialize the search, thereafter
527  * each call to username_find() returns one matching entry.
528  */
529 void
username_find_init(const wchar_t * str,size_t len)530 username_find_init(const wchar_t *str, size_t len)
531 {
532 	int min, med, max, cmp;
533 
534 	ufind.str = str;
535 	ufind.len = len;
536 
537 	if (len == 0) {
538 		ufind.index = 0;
539 		return;
540 	}
541 
542 	for (min = 0, max = utable.cnt - 1; min <= max; ) {
543 		med = (min + max) / 2;
544 		cmp = wcsncmp(str,SDSTR(utable.by_name[med]->login),len);
545 		if (cmp == 0) {
546 			/*
547 			 * the binary search algorithm is slightly altered here,
548 			 * multiple matches are possible, we need the first one
549 			 */
550 			if (min == max) {
551 				ufind.index = med;
552 				return;
553 			}
554 			max = med;
555 		}
556 		else if (cmp < 0)
557 			max = med - 1;
558 		else
559 			min = med + 1;
560 	}
561 
562 	ufind.index = utable.cnt;
563 }
564 
565 const wchar_t *
username_find(const wchar_t ** pgecos)566 username_find(const wchar_t **pgecos)
567 {
568 	const wchar_t *login, *gecos;
569 
570 	if (ufind.index >= utable.cnt)
571 		return 0;
572 	login = SDSTR(utable.by_name[ufind.index]->login);
573 	if (ufind.len && wcsncmp(ufind.str,login,ufind.len))
574 		return 0;
575 	if (pgecos) {
576 		gecos = SDSTR(utable.by_name[ufind.index]->gecos);
577 		*pgecos = *gecos == L'\0' ? 0 : gecos;
578 	}
579 	ufind.index++;
580 	return login;
581 }
582 
583 /* the same find functions() for groups */
584 void
groupname_find_init(const wchar_t * str,size_t len)585 groupname_find_init(const wchar_t *str, size_t len)
586 {
587 	int min, med, max, cmp;
588 
589 	gfind.str = str;
590 	gfind.len = len;
591 
592 	if (len == 0) {
593 		gfind.index = 0;
594 		return;
595 	}
596 
597 	for (min = 0, max = gtable.cnt - 1; min <= max; ) {
598 		med = (min + max) / 2;
599 		cmp = wcsncmp(str,SDSTR(gtable.by_name[med]->group),len);
600 		if (cmp == 0) {
601 			/*
602 			 * the binary search algorithm is slightly altered here,
603 			 * multiple matches are possible, we need the first one
604 			 */
605 			if (min == max) {
606 				gfind.index = med;
607 				return;
608 			}
609 			max = med;
610 		}
611 		else if (cmp < 0)
612 			max = med - 1;
613 		else
614 			min = med + 1;
615 	}
616 
617 	gfind.index = gtable.cnt;
618 }
619 
620 const wchar_t *
groupname_find(void)621 groupname_find(void)
622 {
623 	const wchar_t *group;
624 
625 	if (gfind.index >= gtable.cnt)
626 		return 0;
627 	group = SDSTR(gtable.by_name[gfind.index]->group);
628 	if (gfind.len && wcsncmp(gfind.str,group,gfind.len))
629 		return 0;
630 	gfind.index++;
631 	return group;
632 }
633 
634 void
user_panel_data(void)635 user_panel_data(void)
636 {
637 	int i, j;
638 	size_t len;
639 	const wchar_t *login, *gecos;
640 	uid_t curs;
641 
642 	curs = VALID_CURSOR(panel_user.pd) ? panel_user.users[panel_user.pd->curs].uid : 0;
643 	if (panel_user.pd->filtering)
644 		match_substr_set(panel_user.pd->filter->line);
645 
646 	panel_user.maxlen = 0;
647 	for (i = j = 0; i < utable.cnt; i++) {
648 		if (curs == utable.by_uid[i]->uid)
649 			panel_user.pd->curs = j;
650 		login = SDSTR(utable.by_uid[i]->login);
651 		gecos = SDSTR(utable.by_uid[i]->gecos);
652 		if (panel_user.pd->filtering && !match_substr(login) && !match_substr(gecos))
653 			continue;
654 		panel_user.users[j].uid = utable.by_uid[i]->uid;
655 		panel_user.users[j].login = login;
656 		len = wcslen(login);
657 		if (len > panel_user.maxlen)
658 			panel_user.maxlen = len;
659 		panel_user.users[j++].gecos = gecos;
660 	}
661 
662 	panel_user.pd->cnt = j;
663 }
664 
665 int
user_prepare(void)666 user_prepare(void)
667 {
668 	if (utable.cnt > panel_user.usr_alloc) {
669 		efree(panel_user.users);
670 		panel_user.usr_alloc = utable.cnt;
671 		panel_user.users = emalloc(panel_user.usr_alloc * sizeof(USER_ENTRY));
672 	}
673 
674 	panel_user.pd->filtering = 0;
675 	panel_user.pd->curs = -1;
676 	user_panel_data();
677 	panel_user.pd->top = panel_user.pd->min;
678 	panel_user.pd->curs = 0;
679 
680 	panel = panel_user.pd;
681 	textline = &line_cmd;
682 
683 	return 0;
684 }
685 
686 
687 void
cx_user_paste(void)688 cx_user_paste(void)
689 {
690 	edit_nu_insertstr(panel_user.users[panel_user.pd->curs].login,QUOT_NORMAL);
691 	edit_insertchar(' ');
692 	if (panel->filtering == 1)
693 		cx_filter();
694 }
695 
696 void
cx_user_mouse(void)697 cx_user_mouse(void)
698 {
699 	if (MI_PASTE)
700 		cx_user_paste();
701 }
702 
703 void
group_panel_data(void)704 group_panel_data(void)
705 {
706 	int i, j;
707 	const wchar_t *group;
708 	gid_t curs;
709 
710 	curs = VALID_CURSOR(panel_group.pd) ? panel_group.groups[panel_group.pd->curs].gid : 0;
711 	if (panel_group.pd->filtering)
712 		match_substr_set(panel_group.pd->filter->line);
713 
714 	for (i = j = 0; i < gtable.cnt; i++) {
715 		if (curs == gtable.by_gid[i]->gid)
716 			panel_group.pd->curs = j;
717 		group = SDSTR(gtable.by_gid[i]->group);
718 		if (panel_group.pd->filtering && !match_substr(group))
719 			continue;
720 		panel_group.groups[j].gid = gtable.by_gid[i]->gid;
721 		panel_group.groups[j++].group = group;
722 	}
723 
724 	panel_group.pd->cnt = j;
725 }
726 
727 int
group_prepare(void)728 group_prepare(void)
729 {
730 	if (gtable.cnt > panel_group.grp_alloc) {
731 		efree(panel_group.groups);
732 		panel_group.grp_alloc = gtable.cnt;
733 		panel_group.groups = emalloc(panel_group.grp_alloc * sizeof(GROUP_ENTRY));
734 	}
735 
736 	panel_group.pd->filtering = 0;
737 	panel_group.pd->curs = -1;
738 	group_panel_data();
739 	panel_group.pd->top = panel_group.pd->min;
740 	panel_group.pd->curs = 0;
741 	panel = panel_group.pd;
742 	textline = &line_cmd;
743 
744 	return 0;
745 }
746 
747 void
cx_group_paste(void)748 cx_group_paste(void)
749 {
750 	edit_nu_insertstr(panel_group.groups[panel_group.pd->curs].group,QUOT_NORMAL);
751 	edit_insertchar(' ');
752 	if (panel->filtering == 1)
753 		cx_filter();
754 }
755 
756 void
cx_group_mouse(void)757 cx_group_mouse(void)
758 {
759 	if (MI_PASTE)
760 		cx_group_paste();
761 }
762