check that old data exists when it is necessary
[pam_pcsc_cr.git] / authfile.c
1 /*
2 Copyright (c) 2013 Eugene Crosser
3
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
7
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely, subject to the following restrictions:
11
12     1. The origin of this software must not be misrepresented; you must
13     not claim that you wrote the original software. If you use this
14     software in a product, an acknowledgment in the product documentation
15     would be appreciated but is not required.
16
17     2. Altered source versions must be plainly marked as such, and must
18     not be misrepresented as being the original software.
19
20     3. This notice may not be removed or altered from any source
21     distribution.
22 */
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <pwd.h>
30 #include <time.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <alloca.h>
37 #include "base64.h"
38 #include "authobj.h"
39 #include "authfile.h"
40
41 /*
42  * Template string may contain zero or one '~' and zero or one '?'.
43  * '~' at the beginning of the template string is substituted with
44  * the home directory of the userid. In any other position it is
45  * substituted with the userid itself. There is no way to make the
46  * resulting path contain '~'. If there is more than one '~', or if
47  * the '~' is at the beginning but userid does not resolve via
48  * getpwnam, or '~' is present but the argument is NULL, path_size
49  * returns 0, and make_path returns 1.
50  */
51
52 static const char *template = "~/.pam_cr/auth";
53
54 void authfile_template(const char *str)
55 {
56         template = str;
57 }
58
59 /*
60   I know using these two functions and alloca() in between it ugly, but
61   I like the alternatives even less. =ec
62 */
63
64 static int path_size(const struct passwd *pw)
65 {
66         const char *p;
67
68         if ((p = strchr(template, '~')) != strrchr(template, '~')) return 0;
69         if (p && !pw) return 0;
70         if (p == template) return strlen(template)+strlen(pw->pw_dir)+1;
71         else return strlen(template)+strlen(pw->pw_name)+1;
72 }
73
74 static int
75 make_path(char * const path, const struct passwd *pw)
76 {
77         const char *p;
78         char *q;
79
80         path[0] = '\0';
81         q = path;
82         for (p = template; *p; p++) switch (*p) {
83         case '~':
84                 if (!pw) return 1;
85                 if (p == template) strcpy(q, pw->pw_dir);
86                 else strcpy(q, pw->pw_name);
87                 while (*q) q++;
88                 break;
89         default:
90                 *q++ = *p;
91                 break;
92         }
93         *q = '\0';
94         return 0;
95 }
96
97 int parse(char * const buf, const int argc, const char *argv[const])
98 {
99         char *p, *q;
100         int i;
101
102         for (i = 0, p = buf; *p; p = q+1, i++) {
103                 for (q = p; *q && *q != ':' && *q != '\r' && *q != '\n'; q++) ;
104                 *q = '\0';
105                 if (*p && i < argc) argv[i] = p;
106         }
107         return i != argc;
108 }
109
110 struct _auth_obj authfile(const char *userid, const char *password,
111                 void (*update_nonce)(char *nonce, const int nonsize),
112                 const unsigned char *secret, const int secsize,
113                 const unsigned char *payload, const int paylsize,
114                 struct _auth_chunk (*fetch_key)(const unsigned char *chal,
115                                                 const int csize))
116 {
117         struct _auth_obj ret = {0};
118         const struct passwd *pw = NULL;
119         mode_t oldmask;
120         FILE *fp = NULL;
121         char *fn, *nfn;
122         int fnl;
123         struct stat st = {0};
124         char *buf = NULL;
125         struct {
126                 const char *userid;
127                 const char *nonce;
128                 const char *hablob;
129         } w = {NULL, NULL, NULL};
130         unsigned char *ablob = NULL;
131         int blobsize = 0;
132         char *newnonce;
133         int nonsize;
134         struct _auth_obj ao;
135
136         if (userid) pw = getpwnam(userid);
137         if ((fnl = path_size(pw)) == 0) {
138                 ret.err = "authfile path_size failed";
139                 return ret;
140         }
141         fn = alloca(fnl);
142         if (make_path(fn, pw)) {
143                 ret.err = "authfile make_path failed";
144                 return ret;
145         }
146         nfn = alloca(fnl+32);
147         snprintf(nfn, fnl+32, "%s.%d.%ld", fn, (int)getpid(), (long)time(NULL));
148         fp = fopen(fn, "r");
149         if (fp) {
150                 if (fstat(fileno(fp), &st)) st.st_size = 2047;
151                 if (st.st_size > 2047) st.st_size = 2047;
152                 buf = alloca(st.st_size + 1);
153                 if (!fgets(buf, st.st_size + 1, fp)) {
154                         ret.err = strerror(errno);
155                 } else if (parse(buf, sizeof(w)/sizeof(char*),
156                                         (const char ** const)&w)){
157                         ret.err = "error: unparseable auth file";
158                 }
159                 fclose(fp);
160         }
161         if (ret.err) return ret;
162
163         if (w.hablob) {
164                 blobsize = strlen(w.hablob)*3/4;
165                 ablob = alloca(blobsize);
166                 if (b64_decode(w.hablob, ablob, &blobsize))
167                         ret.err = "error: undecodeable auth string";
168         }
169         if (ret.err) return ret;
170
171         nonsize = w.nonce ? strlen(w.nonce)*2 : 32;
172         if (nonsize < 32) nonsize = 32;
173         newnonce = alloca(nonsize);
174         if (w.nonce) strcpy(newnonce, w.nonce);
175         else memset(newnonce, 0, nonsize);
176         update_nonce(newnonce, nonsize);
177
178         ao = authobj(userid?userid:w.userid, password,
179                         w.nonce, newnonce, secret, secsize,
180                         payload, paylsize, ablob, blobsize,
181                         fetch_key);
182
183         if (ao.err) {
184                 ret.err = ao.err;
185                 if (ao.data) memset(ao.data, 0, ao.datasize);
186                 if (ao.payload) memset(ao.payload, 0, ao.paylsize);
187                 if (ao.buffer) free(ao.buffer);
188                 return ret;
189         }
190
191         oldmask = umask(077);
192         if ((fp = fopen(nfn, "w"))) {
193                 int bsize = ((ao.datasize-1)/3+1)*4+1;
194                 char *b64 = alloca(bsize);
195
196                 if (b64_encode(ao.data, ao.datasize, b64, &bsize)) {
197                         ret.err = "error: could not encode auth string";
198                 } else if (fprintf(fp, "%s:%s:%s\n",
199                                 userid?userid:w.userid, newnonce, b64) < 0) {
200                         ret.err = strerror(errno);
201                 }
202                 if (st.st_uid || st.st_gid) {
203                         if (fchown(fileno(fp), st.st_uid, st.st_gid)) /*ign*/;
204                 }
205                 if (fclose(fp) < 0) {
206                         ret.err = strerror(errno);
207                 }
208         } else {
209                 ret.err = strerror(errno);
210         }
211         (void)umask(oldmask);
212         if (ret.err) {
213                 unlink(nfn); /* may not exist but no matter */
214         } else if (rename(nfn, fn)) {
215                 ret.err = strerror(errno);
216         }
217
218         if (!ret.err) {
219                 int bufsize = (w.userid?strlen(w.userid)+1:0) + ao.paylsize + 1;
220                 if (bufsize) {
221                         if ((ret.buffer = malloc(bufsize)) == NULL) {
222                                 ret.err = "authfile malloc failed";
223                         } else {
224                                 unsigned char *p = ret.buffer;
225                                 if (w.userid) {
226                                         strcpy((char*)p, w.userid);
227                                         ret.data = p;
228                                         ret.datasize = strlen(w.userid)+1;
229                                         p += strlen(w.userid)+1;
230                                 }
231                                 if (ao.payload) {
232                                         memcpy(p, ao.payload, ao.paylsize);
233                                         p[ao.paylsize] = '\0';
234                                         ret.payload = p;
235                                         ret.paylsize = ao.paylsize+1;
236                                 }
237                         }
238                 }
239         }
240
241         if (ao.data) memset(ao.data, 0, ao.datasize);
242         if (ao.payload) memset(ao.payload, 0, ao.paylsize);
243         if (ao.buffer) free(ao.buffer);
244         return ret;
245 }