1 /* password.c - password read/update helper functions.
2 *
3 * Copyright 2012 Ashwini Kumar <[email protected]>
4 */
5
6 #include "toys.h"
7
8 // generate $id$salt$hash given a password and algorithm. Generates salt if NULL
9 //char *toy_crypt(char *pass, char *salt, char *algo)
10 // generate ID prefix and random salt for given encryption algorithm.
get_salt(char * salt,char * algo,int rand)11 int get_salt(char *salt, char *algo, int rand)
12 {
13 struct {
14 char *type, id, len;
15 } al[] = {{"des", 0, 2}, {"md5", 1, 8}, {"sha256", 5, 16}, {"sha512", 6, 16}};
16 char *s;
17 int i, len;
18
19 for (i = 0; i < ARRAY_LEN(al); i++) {
20 for (s = al[i].type, len = 0; algo[len]; len++) {
21 while (ispunct(algo[len])) len++;
22 if (tolower(algo[len]) != tolower(*s++)) break;
23 }
24 if (algo[len]) continue;
25
26 len = al[i].len;
27 s = salt + (al[i].id ? sprintf(salt, "$%c$", '0'+al[i].id) : 0);
28
29 // Read appropriate number of random bytes for salt
30 if (rand) xgetrandom(libbuf, ((len*6)+7)/8);
31
32 // Grab 6 bit chunks and convert to characters in ./0-9a-zA-Z
33 for (i = 0; i<len; i++) {
34 int bitpos = i*6, bits = bitpos/8;
35
36 bits = ((libbuf[i]+(libbuf[i+1]<<8)) >> (bitpos&7)) & 0x3f;
37 bits += 46;
38 if (bits > 57) bits += 7;
39 if (bits > 90) bits += 6;
40
41 s[i] = bits;
42 }
43 s[len] = 0;
44
45 return s-salt;
46 }
47
48 return -1;
49 }
50
51 // Prompt with mesg, read password into buf, return 0 for success 1 for fail
read_password(char * buf,int buflen,char * mesg)52 int read_password(char *buf, int buflen, char *mesg)
53 {
54 struct termios oldtermio;
55 struct sigaction sa = {.sa_handler = generic_signal}, oldsa;
56 int i, tty = tty_fd(), ret = 1;
57
58 // Set NOP signal handler to return from the read.
59 fflush(0);
60 sigaction(SIGINT, &sa, &oldsa);
61 tcflush(tty, TCIFLUSH);
62 xset_terminal(tty, 1, 0, &oldtermio);
63 dprintf(tty, "%s", mesg);
64
65 // Loop assembling password. (Too long = fail)
66 for (i = 0; i<buflen-1; i++) {
67 // tty closed, or EOF or ctrl-D at start, or ctrl-C anywhere: fail.
68 if ((ret = read(tty, buf+i, 1))<0 || (!ret&&!i) || *buf==4 || buf[i]==3)
69 break;
70 // EOF or newline: return success
71 else if (!ret || buf[i]=='\n' || buf[i]=='\r') {
72 ret = 0;
73 break;
74 } else if (buf[i] == 8 || buf[i] == 127) i -= 2-!i;
75 }
76
77 // Restore terminal/signal state, terminate string
78 tcsetattr(0, TCSANOW, &oldtermio);
79 sigaction(SIGINT, &oldsa, 0);
80 xputc('\n');
81 fflush(0);
82 buf[i*!ret] = 0;
83
84 return ret;
85 }
86
87 // Read array of colon separated strings from file with given first entry
get_userline(char * filename,char * username)88 char **get_userline(char *filename, char *username)
89 {
90 FILE *fp = xfopen(filename, "r");
91 int len = strlen(username);
92 char *line = 0, **data;
93 size_t n = 0;
94
95 while (getline(&line, &n, fp)) {
96 if (!strncmp(line, username, len) && line[len]==':') {
97 data = xzalloc(10*sizeof(char *));
98 for (len = 0; len<9; len++) {
99 data[len] = line;
100 if (!(line = strchr(line, ':'))) break;
101 *line++ = 0;
102 }
103 return data;
104 }
105 memset(line, 0, strlen(line));
106 free(line);
107 line = 0;
108 n = 0;
109 }
110
111 return 0;
112 }
113
114 // update colon-separated text files ala /etc/{passwd,shadow,group,gshadow}
115 // returns 1 for success, 0 for failure
116
117 // username = string match for first entry in line
118 // entry = new entry (NULL deletes matching line, contains : adds/replaces line)
119 // pos = if no : in "entry", which field to replace (0 is first)
120 // filename+ = new copy being written, filename- = backup of old version
121
update_password(char * filename,char * username,char * entry,int pos)122 int update_password(char *filename, char *username, char *entry, int pos)
123 {
124 char *ff = xmprintf("%s-", filename), *line = 0, *start, *end, *out, *oo;
125 FILE *ofp;
126 int len = strlen(username)*!strchr(username,':'), rc = 0, ret = 0, found = 0,
127 nfd;
128 struct flock lock = {.l_type = F_WRLCK};
129 struct stat st;
130 long long ll = 0;
131
132 // Open old filename ("r" won't let us lock) and get blocking lock
133 if (!(ofp = fopen(filename, "w+")) || 0>fcntl(fileno(ofp), F_SETLK, &lock)\
134 || fstat(fileno(ofp), &st))
135 {
136 perror_msg_raw(filename);
137 goto free_storage;
138 }
139
140 // Delete old backup, link new backup. (Failure here isn't fatal.)
141 unlink(ff);
142 if (0>link(filename, ff)) perror_msg_raw(ff);
143
144 // Open new file to copy entries to
145 ff[strlen(ff)-1] = '+';
146 if (-1 == (nfd = xcreate(ff, O_CREAT|O_EXCL|O_WRONLY, st.st_mode))) {
147 perror_msg_raw(ff);
148 goto free_storage;
149 }
150
151 // Loop through lines
152 for (; getline(&line, (void *)&ll, ofp)!=-1; memset(line, 0, strlen(line))) {
153 // find matching line
154 oo = 0;
155 start = end = chomp(line);
156 if (strncmp(line, username, len) || !(line[len] && line[len]!=':'))
157 out = line;
158 else {
159 found++;
160
161 // Delete or replace whole line?
162 if (!entry) out = 0;
163 else if (strchr(entry, ':')) out = entry;
164
165 // Replace entry at pos
166 else {
167 for (;; pos--, start = ++end) {
168 while (*end && *end != ':') end++;
169 if (!pos || !*end) break;
170 }
171 if (pos>=0) out = line;
172 else oo = out = xmprintf("%*s%s%s\n", (int)(start-line),line,entry,end);
173 }
174 }
175 if (out) {
176 rc = dprintf(nfd, "%s\n", out);
177 free(out);
178 if (rc<0) {
179 perror_msg_raw(ff);
180 goto free_storage;
181 }
182 free(oo);
183 }
184 }
185 free(line);
186 if (!found && entry && strchr(entry, ':')) dprintf(nfd, "%s\n", entry);
187 fsync(nfd);
188 close(nfd); // automatically unlocks
189
190 if (!found || rename(ff, filename)) {
191 if (found) perror_msg("%s -> %s", ff, filename);
192 else if (entry) error_msg("No %s in %s", username, filename);
193 } else ret = 1, *ff = 0;
194
195 free_storage:
196 if (ofp) fclose(ofp);
197 if (ff && *ff) unlink(ff);
198 free(ff);
199
200 return ret;
201 }
202