some pending cosmetics
[webgit] / src / account.c
1 /*
2     cehtehs git web frontend
3
4   Copyright (C)
5     2007, 2008,         Christian Thaeter <ct@pipapo.org>
6
7   This program is free software: you can redistribute it and/or modify
8   it under the terms of the GNU Affero General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU Affero General Public License for more details.
16
17   You should have received a copy of the GNU Affero General Public License
18   along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "account.h"
22 #include "actions.h"
23
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <cwa.h>
27
28 #define SHA1_HEADER <openssl/sha.h>
29 #include "git/cache.h"
30
31 Html
32 webgit_account_link (struct webgit_query* query, Html text)
33 {
34   return html (
35                html_tag ("a",
36                          html_attr ("href", html_fmt ("%s?action=account", query->request->script_name))
37                          ),
38                text
39                );
40 }
41
42
43 Html
44 webgit_account_logout_link (struct webgit_query* query, Html text)
45 {
46   return html (
47                html_tag ("a",
48                          html_attr ("href", html_fmt ("%s?expire=0", query->request->script_name))
49                          ),
50                    text
51                );
52 }
53
54
55 size_t
56 webgit_account_user_validate (const char* user)
57 {
58   size_t len = 0;
59
60   if (!user)
61     return 0;
62
63   while (*user)
64     {
65       if (!islower (*user) || !isascii (*user))
66         return 0;
67
68       ++len;
69       ++user;
70     }
71
72   if (len < 2 || len > 31)
73     return 0;
74
75   return len;
76 }
77
78
79 size_t
80 webgit_account_name_validate (const char* name)
81 {
82   size_t len = 0;
83
84   if (!name)
85     return 0;
86
87   if (!strchr (name, ' '))
88     return 0;
89
90   while (*name)
91     {
92       if (iscntrl (*name))
93         return 0;
94
95       ++len;
96       ++name;
97     }
98
99   if (len < 4 || len > 255)
100     return 0;
101
102   return len;
103 }
104
105
106 size_t
107 webgit_account_email_validate (const char* email)
108 {
109   size_t len = 0;
110
111   if (!email)
112     return 0;
113
114   if (!strchr (email, '@'))
115     return 0;
116
117   while (*email)
118     {
119       if (isspace (*email) || iscntrl (*email))
120         return 0;
121
122       ++len;
123       ++email;
124     }
125
126   if (len < 6 || len > 255)
127     return 0;
128
129   return len;
130 }
131
132 const char*
133 webgit_account_signature (struct webgit_query* query)
134 {
135   if (!(query->user && query->name && query->email))
136     return NULL;
137
138   size_t buf_len =
139     strlen (query->user) + strlen (query->name) + strlen (query->email) + 40 /*secret*/ + sizeof ("; ; ; ");
140
141   char* buf = cwa_buffer_provide (buf_len);
142
143   sprintf (buf, "%s; %s; %s; %s", query->user, query->name, query->email, query->secret);
144   char* ret = sha1_to_hex (SHA1 ((unsigned char*)buf, buf_len, NULL));
145   memset (buf, 0, buf_len);
146
147   return ret;
148 }
149
150
151 int
152 webgit_account_validate_signature (struct webgit_query* query)
153 {
154   const char* sig = webgit_account_signature (query);
155   if (!sig || !query->ssign)
156     return 0;
157
158   return !strcmp (sig, query->ssign);
159 }
160
161
162 int
163 webgit_account_sendmail (struct webgit_query* query)
164 {
165   /* TODO: sendmail as config option */
166   size_t len = snprintf (NULL, 0, "/usr/lib/sendmail %s", query->email);
167   char* mail = cwa_buffer_provide (len+1);
168   sprintf (mail, "/usr/lib/sendmail %s", query->email);
169
170   FILE* sendmail = popen (mail, "w");
171
172   fprintf (sendmail,
173            "Subject: %s webgit account activation\n"
174            "From: Webgit\n"
175            "To: %s"
176            "\n"
177            "TODO: Explain login ...\n"
178            "To (re)activate your account visit the following link:\n"
179            "http://%s%s?user=%s&name=%s&email=%s&ssign=%s\n"
180            "\n",
181
182            query->request->host_name,
183            query->email,
184
185            query->request->host_name,
186            query->request->script_name,
187            query->user,
188            cwa_urlencode (query->name),
189            cwa_urlencode (query->email),
190            webgit_account_signature (query)
191            );
192
193   return pclose (sendmail);
194 }
195
196
197 int
198 webgit_account_create_pending (struct webgit_query* query)
199 {
200   char * path = cwa_buffer_provide (strlen (query->accountdir) + sizeof ("/x/") + strlen (query->user));
201   char * path_pend = cwa_buffer_provide (strlen (query->accountdir) + sizeof ("/pending/") + strlen (query->user));
202
203   /* TODO: query->accountdir/revoked/ holds permanently disabled accounts */
204
205   /* make directories when not already existing, errors are ignored */
206   sprintf (path, "%s/%c", query->accountdir, *query->user);
207   mkdir (path, 0770);
208   sprintf (path_pend, "%s/pending", query->accountdir);
209   mkdir (path_pend, 0770);
210
211   sprintf (path, "%s/%c/%s", query->accountdir, *query->user, query->user);
212   sprintf (path_pend, "%s/pending/%s", query->accountdir, query->user);
213
214   FILE* f = fopen (path_pend, "wx");
215
216   if (!access (path, F_OK) || !f)
217     {
218       /* account already exist or pending */
219       if (f)
220         {
221           fclose (f);
222           unlink (path_pend);
223         }
224       die ("account already exist");
225       return -1;
226     }
227   else
228     {
229       /* ok, lets make it pending */
230       fprintf (f, "user=%s; name=\"%s\"; email=%s;\n", query->user, query->name, query->email);
231       fclose (f);
232
233       /* send email */
234       return webgit_account_sendmail (query);
235     }
236 }
237
238
239 int
240 webgit_account_recover (struct webgit_query* query)
241 {
242   char * path = cwa_buffer_provide (strlen (query->accountdir) + sizeof ("/x/") + strlen (query->user));
243   sprintf (path, "%s/%c/%s", query->accountdir, *query->user, query->user);
244
245   FILE* f = fopen (path, "r");
246   if (!f)
247     die ("account does not exist");
248
249
250   char* name = cwa_buffer_provide (256);
251   char* email = cwa_buffer_provide (256);
252
253   int n = fscanf (f, "user=%*31[^;]; name=\"%255[^\"]\"; email=%255[^;];\n", name, email);
254   fclose (f);
255
256   if (n != 2)
257       die ("parse error");
258
259   query->name = cwa_strndup (name, 255);
260   query->email = cwa_strndup (email, 255);
261
262   return webgit_account_sendmail (query);
263 }
264
265
266
267
268 Html
269 webgit_account_menu_action (struct webgit_query* query)
270 {
271   Html menu = html_list ();
272
273   if (query->accountdir && query->secret)
274     {
275       if (query->user && query->ssign)
276         {
277           html_list_append (menu,
278                             webgit_account_logout_link (query, html ("Logout")));
279         }
280     }
281   else
282     html_list_append (menu, "User accounts not enabled on this server<br/>");
283
284   return html (
285                "TODO: webgit logo <br />",
286                menu, "<br />",
287                webgit_main_link (query, html ("Main")), "<br />"
288                );
289 }
290
291
292 Html
293 webgit_account_content_action (struct webgit_query* query)
294 {
295   Html content = html_list ();
296
297   if (query->accountdir && query->secret)
298     {
299       if (!query->user)
300         {
301           /* create account */
302           html_list_append (content,
303                             "TODO: Explain account creation, cookies etc...",
304                             html (
305                                   html_tag ("form",
306                                             html_attr ("action", html_str (query->request->script_name)),
307                                             html_attr ("method","get")
308                                             ),
309                                   html_hidden ("action", "account"),
310                                   "Username: (2-32 lowercase ascii chars)<br/>",
311                                   html (
312                                         html_tag ("input", html_attr ("name", "user"))
313                                         ),
314                                   html (
315                                         html_tag ("input",
316                                                   html_attr ("name", "recover"),
317                                                   html_attr ("type", "submit"),
318                                                   html_attr ("value", "Recover Account"))
319                                         ), "<br/>",
320                                   "Real Name: (must contain at least one space, utf8)<br/>",
321                                   html (
322                                         html_tag ("input", html_attr ("name", "name"))
323                                         ), "<br/>",
324                                   "Email: (must contain a @, used to recover account)<br/>",
325                                   html (
326                                         html_tag ("input", html_attr ("name", "email"))
327                                         ), "<br/>",
328                                   html (
329                                         html_tag ("input",
330                                                   html_attr ("name", "create"),
331                                                   html_attr ("type", "submit"),
332                                                   html_attr ("value", "Create Account"))
333                                         )
334                                   )
335                             );
336         }
337       else if (query->user)
338         {
339           if (!query->ssign)
340             {
341               /* create 2nd step, must not exist send signed cookie */
342               if (!webgit_account_user_validate (query->user))
343                 die ("invalid user name");
344
345               if (query->recover)
346                 {
347                   webgit_account_recover (query);
348
349                   html_list_append (content, "account recovered, email send");
350                 }
351               else
352                 {
353                   if (!webgit_account_name_validate (query->name))
354                     die ("invalid name");
355                   if (!webgit_account_email_validate (query->email))
356                     die ("invalid email");
357
358                   webgit_account_create_pending (query);
359
360                   html_list_append (content, "account created, email send");
361                 }
362             }
363           else
364             {
365               /* change name/email */
366
367               html_list_append (content,
368                                 "TODO: preferences"
369                                 );
370             }
371         }
372     }
373   else
374     html_list_append (content, "User accounts not enabled on this server<br/>");
375
376   return content;
377 }
378
379
380 /*
381 //      Local Variables:
382 //      mode: C
383 //      c-file-style: "gnu"
384 //      indent-tabs-mode: nil
385 //      End:
386 */