version 0.9.2
[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. '?' is substituted with the
46  * tokenid. There is no way to make the resulting path contain '~'
47  * or '?'. If there is more than one '~' or '?', or if the '~' is
48  * at the beginning but userid does not resolve via getpwnam, or
49  * the character to substitute is present but the argument is NULL,
50  * NULL is returned. Otherwise, malloc()'ed area containg the path
51  * string.
52  */
53
54 static const char *template = "~/.pam_cr/auth";
55
56 void authfile_template(const char *str)
57 {
58         template = str;
59 }
60
61 /*
62   I know using these two functions and alloca() in between it ugly, but
63   I like the alternatives even less. =ec
64 */
65
66 static int path_size(const char *tokenid, const struct passwd *pw)
67 {
68         const char *usub;
69         const char *p, *q;
70
71         if ((p = strchr(template, '~')) != strrchr(template, '~')) return 0;
72         if ((q = strchr(template, '?')) != strrchr(template, '?')) return 0;
73         if (p && !pw) return 0;
74         if (q && !tokenid) return 0;
75         if (p == template) usub = pw->pw_dir;
76         else usub = pw->pw_name;
77         return strlen(template)+(p?strlen(usub):0)+(q?strlen(tokenid):0)+1;
78 }
79
80 static int
81 make_path(char * const path, const char *tokenid, const struct passwd *pw)
82 {
83         const char *p;
84         char *q;
85
86         path[0] = '\0';
87         q = path;
88         for (p = template; *p; p++) switch (*p) {
89         case '~':
90                 if (!pw) return 1;
91                 if (p == template) strcpy(q, pw->pw_dir);
92                 else strcpy(q, pw->pw_name);
93                 while (*q) q++;
94                 break;
95         case '?':
96                 if (!tokenid) return 1;
97                 strcpy(q, tokenid);
98                 while (*q) q++;
99                 break;
100         default:
101                 *q++ = *p;
102                 break;
103         }
104         *q = '\0';
105         return 0;
106 }
107
108 int parse(char * const buf, const int argc, const char *argv[const])
109 {
110         char *p, *q;
111         int i;
112
113         for (i = 0, p = buf; *p; p = q+1, i++) {
114                 for (q = p; *q && *q != ':' && *q != '\r' && *q != '\n'; q++) ;
115                 *q = '\0';
116                 if (*p && i < argc) argv[i] = p;
117         }
118         return i != argc;
119 }
120
121 struct _auth_obj authfile(const char *tokenid,
122                 const char *userid, const char *password,
123                 void (*update_nonce)(char *nonce, const int nonsize),
124                 const unsigned char *secret, const int secsize,
125                 const unsigned char *payload, const int paylsize,
126                 struct _auth_chunk (*fetch_key)(const unsigned char *chal,
127                                                 const int csize))
128 {
129         struct _auth_obj ret = {0};
130         const struct passwd *pw = NULL;
131         mode_t oldmask;
132         FILE *fp = NULL;
133         char *fn, *nfn;
134         int fnl;
135         struct stat st = {0};
136         char *buf = NULL;
137         struct {
138                 const char *tokenid;
139                 const char *userid;
140                 const char *nonce;
141                 const char *hablob;
142         } w = {"", NULL, NULL, NULL};
143         unsigned char *ablob = NULL;
144         int blobsize = 0;
145         char *newnonce;
146         int nonsize;
147         struct _auth_obj ao;
148
149         if (userid) pw = getpwnam(userid);
150         if ((fnl = path_size(tokenid, pw)) == 0) {
151                 ret.err = "authfile path_size failed";
152                 return ret;
153         }
154         fn = alloca(fnl);
155         if (make_path(fn, tokenid, pw)) {
156                 ret.err = "authfile make_path failed";
157                 return ret;
158         }
159         nfn = alloca(fnl+32);
160         snprintf(nfn, fnl+32, "%s.%d.%ld", fn, (int)getpid(), (long)time(NULL));
161         fp = fopen(fn, "r");
162         if (fp) {
163                 if (fstat(fileno(fp), &st)) st.st_size = 2047;
164                 if (st.st_size > 2047) st.st_size = 2047;
165                 buf = alloca(st.st_size + 1);
166                 if (!fgets(buf, st.st_size + 1, fp)) {
167                         ret.err = strerror(errno);
168                 } else if (parse(buf, sizeof(w)/sizeof(char*),
169                                         (const char ** const)&w)){
170                         ret.err = "error: unparseable auth file";
171                 }
172                 fclose(fp);
173         }
174         if (ret.err) return ret;
175
176         if (w.hablob) {
177                 blobsize = strlen(w.hablob)*3/4;
178                 ablob = alloca(blobsize);
179                 if (b64_decode(w.hablob, ablob, &blobsize))
180                         ret.err = "error: undecodeable auth string";
181         }
182         if (ret.err) return ret;
183
184         nonsize = w.nonce ? strlen(w.nonce)*2 : 32;
185         if (nonsize < 32) nonsize = 32;
186         newnonce = alloca(nonsize);
187         if (w.nonce) strcpy(newnonce, w.nonce);
188         else memset(newnonce, 0, nonsize);
189         update_nonce(newnonce, nonsize);
190
191         ao = authobj(userid?userid:w.userid, password,
192                         w.nonce, newnonce, secret, secsize,
193                         payload, paylsize, ablob, blobsize,
194                         fetch_key);
195
196         if (ao.err) {
197                 ret.err = ao.err;
198                 if (ao.data) memset(ao.data, 0, ao.datasize);
199                 if (ao.payload) memset(ao.payload, 0, ao.paylsize);
200                 if (ao.buffer) free(ao.buffer);
201                 return ret;
202         }
203
204         oldmask = umask(077);
205         if ((fp = fopen(nfn, "w"))) {
206                 int bsize = ((ao.datasize-1)/3+1)*4+1;
207                 char *b64 = alloca(bsize);
208
209                 if (b64_encode(ao.data, ao.datasize, b64, &bsize)) {
210                         ret.err = "error: could not encode auth string";
211                 } else if (fprintf(fp, "%s:%s:%s:%s\n",
212                                 tokenid?tokenid:w.tokenid,
213                                 userid?userid:w.userid, newnonce, b64) < 0) {
214                         ret.err = strerror(errno);
215                 }
216                 if (st.st_uid || st.st_gid) {
217                         if (fchown(fileno(fp), st.st_uid, st.st_gid)) /*ign*/;
218                 }
219                 if (fclose(fp) < 0) {
220                         ret.err = strerror(errno);
221                 }
222         } else {
223                 ret.err = strerror(errno);
224         }
225         (void)umask(oldmask);
226         if (ret.err) {
227                 unlink(nfn); /* may not exist but no matter */
228         } else if (rename(nfn, fn)) {
229                 ret.err = strerror(errno);
230         }
231
232         if (!ret.err) {
233                 int bufsize = (w.userid?strlen(w.userid)+1:0) + ao.paylsize + 1;
234                 if (bufsize) {
235                         if ((ret.buffer = malloc(bufsize)) == NULL) {
236                                 ret.err = "authfile malloc failed";
237                         } else {
238                                 unsigned char *p = ret.buffer;
239                                 if (w.userid) {
240                                         strcpy((char*)p, w.userid);
241                                         ret.data = p;
242                                         ret.datasize = strlen(w.userid)+1;
243                                         p += strlen(w.userid)+1;
244                                 }
245                                 if (ao.payload) {
246                                         memcpy(p, ao.payload, ao.paylsize);
247                                         p[ao.paylsize] = '\0';
248                                         ret.payload = p;
249                                         ret.paylsize = ao.paylsize+1;
250                                 }
251                         }
252                 }
253         }
254
255         if (ao.data) memset(ao.data, 0, ao.datasize);
256         if (ao.payload) memset(ao.payload, 0, ao.paylsize);
257         if (ao.buffer) free(ao.buffer);
258         return ret;
259 }