1 /*
2 ** Copyright (c) 2002 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the GNU General Public
6 ** License as published by the Free Software Foundation; either
7 ** version 2 of the License, or (at your option) any later version.
8 **
9 ** This program is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 ** General Public License for more details.
13 **
14 ** You should have received a copy of the GNU General Public
15 ** License along with this library; if not, write to the
16 ** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 ** Boston, MA  02111-1307, USA.
18 **
19 ** Author contact information:
20 **   drh@hwaci.com
21 **   http://www.hwaci.com/drh/
22 **
23 *******************************************************************************
24 **
25 ** Routines for handling user account
26 */
27 #define _XOPEN_SOURCE
28 #include <unistd.h>
29 #include "config.h"
30 #include "user.h"
31 
32 /*
33 ** WEBPAGE: /userlist
34 */
user_list(void)35 void user_list(void){
36   char **azResult;
37   int i;
38 
39   login_check_credentials();
40   if( !g.okWrite && g.isAnon ){
41     login_needed();
42     return;
43   }
44   common_standard_menu("userlist", 0);
45   common_add_help_item("CvstracAdminUsers");
46   common_add_action_item("useredit", "Add User");
47   common_header("User List");
48   @ <table cellspacing=0 cellpadding=0 border=0>
49   @ <tr>
50   @   <th align="right"><nobr>User ID</nobr></th>
51   @   <th>&nbsp;&nbsp;&nbsp;Permissions&nbsp;&nbsp;&nbsp;</th>
52   @   <th><nobr>In Real Life</nobr></th>
53   @ </tr>
54   azResult = db_query(
55     "SELECT id, name, email, capabilities FROM user ORDER BY id");
56   for(i=0; azResult[i]; i+= 4){
57     @ <tr>
58     @ <td align="right">
59     if( g.okAdmin ){
60       @ <a href="useredit?id=%t(azResult[i])">
61     }
62     @ <nobr>%h(azResult[i])</nobr>
63     if( g.okAdmin ){
64       @ </a>
65     }
66     @ </td>
67     @ <td align="center">%s(azResult[i+3])</td>
68     if( azResult[i+2] && azResult[i+2][0] ){
69       char *zE = azResult[i+2];
70       @ <td align="left"><nobr>%h(azResult[i+1])
71       @    (<a href="mailto:%h(zE)">%h(zE)</a>)</nobr></td>
72     } else {
73       @ <td align="left"><nobr>%h(azResult[i+1])</nobr></td>
74     }
75     @ </tr>
76   }
77   @ </table>
78   @ <p><hr>
79   @ <b>Notes:</b>
80   @ <ol>
81   @ <li><p>The permission flags are as follows:</p>
82   @ <table>
83   @ <tr><td>a</td><td width="10"></td>
84   @     <td>Admin: Create or delete users and ticket report formats</td></tr>
85   @ <tr><td>d</td><td></td>
86   @     <td>Delete: Erase anonymous wiki, tickets, and attachments</td></tr>
87   @ <tr><td>i</td><td></td>
88   @     <td>Check-in: Add new code to the %h(g.scm.zName) repository</td></tr>
89   @ <tr><td>j</td><td></td><td>Read-Wiki: View wiki pages</td></tr>
90   @ <tr><td>k</td><td></td><td>Wiki: Create or modify wiki pages</td></tr>
91   @ <tr><td>n</td><td></td><td>New: Create new tickets</td></tr>
92   @ <tr><td>o</td><td></td>
93   @     <td>Check-out: Read code out of the %h(g.scm.zName) repository</td></tr>
94   @ <tr><td>p</td><td></td><td>Password: Change password</td></tr>
95   @ <tr><td>q</td><td></td><td>Query: Create or edit report formats</td></tr>
96   @ <tr><td>r</td><td></td><td>Read: View tickets and change histories</td></tr>
97   @ <tr><td>s</td><td></td><td>Setup: Change CVSTrac options</td></tr>
98   @ <tr><td>w</td><td></td><td>Write: Edit tickets</td></tr>
99   @ </table>
100   @ </p></li>
101   @
102   @ <li><p>
103   @ If a user named "<b>anonymous</b>" exists, then anyone can access
104   @ the server without having to log in.  The permissions on the
105   @ anonymous user determine the access rights for anyone who is not
106   @ logged in.
107   @ </p></li>
108   @
109   if( !strcmp(g.scm.zSCM,"cvs") ){
110     @ <li><p>
111     @ You must be using CVS version 1.11 or later in order to give users
112     @ read-only access to the repository.
113     @ With earlier versions of CVS, all users with check-out
114     @ privileges also automatically get check-in privileges.
115     @ </p></li>
116     @
117     @ <li><p>
118     @ Changing a users ID or password modifies the <b>CVSROOT/passwd</b>,
119     @ <b>CVSROOT/readers</b>, and <b>CVSROOT/writers</b> files in the CVS
120     @ repository, if those files have write permission turned on.  Users
121     @ IDs in <b>CVSROOT/passwd</b> that are unknown to CVSTrac are preserved.
122     if( g.okSetup ){
123       @ Use the "Import CVS Users" button on the
124       @ <a href="setup_user">user setup</a> page
125       @ to import CVS users into CVSTrac.
126     }
127     @ </p></li>
128   }
129   @ </ol>
130   common_footer();
131 }
132 
133 /*
134 ** WEBPAGE: /useredit
135 */
user_edit(void)136 void user_edit(void){
137   char **azResult;
138   const char *zId, *zName, *zEMail, *zCap;
139   char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap ;
140   char *oak, *oad, *oaq;
141   int doWrite;
142   int higherUser = 0;  /* True if user being edited is SETUP and the */
143                        /* user doing the editing is ADMIN.  Disallow editing */
144 
145   /* Must have ADMIN privleges to access this page
146   */
147   login_check_credentials();
148   if( !g.okAdmin ){ login_needed(); return; }
149 
150   /* Check to see if an ADMIN user is trying to edit a SETUP account.
151   ** Don't allow that.
152   */
153   zId = P("id");
154   if( zId && !g.okSetup ){
155     char *zOldCaps;
156     zOldCaps = db_short_query(
157        "SELECT capabilities FROM user WHERE id='%q'",zId);
158     higherUser = zOldCaps && strchr(zOldCaps,'s');
159   }
160 
161   if( !higherUser ){
162     if( P("delete") ){
163       common_add_action_item("userlist", "Cancel");
164       common_header("Are You Sure?");
165       @ <form action="useredit" method="POST">
166       @ <p>You are about to delete the user <strong>%h(zId)</strong> from
167       @ the database.  This is an irreversible operation.</p>
168       @
169       @ <input type="hidden" name="id" value="%t(zId)">
170       @ <input type="hidden" name="nm" value="">
171       @ <input type="hidden" name="em" value="">
172       @ <input type="hidden" name="pw" value="">
173       @ <input type="submit" name="delete2" value="Delete The User">
174       @ <input type="submit" name="can" value="Cancel">
175       @ </form>
176       common_footer();
177       return;
178     }else if( P("can") ){
179       cgi_redirect("userlist");
180       return;
181     }
182   }
183 
184   /* If we have all the necessary information, write the new or
185   ** modified user record.  After writing the user record, redirect
186   ** to the page that displays a list of users.
187   */
188   doWrite = zId && zId[0] && cgi_all("nm","em","pw") && !higherUser;
189   if( doWrite ){
190     const char *zOldPw;
191     char zCap[20];
192     int i = 0;
193     int aa = P("aa")!=0;
194     int ad = P("ad")!=0;
195     int ai = P("ai")!=0;
196     int aj = P("aj")!=0;
197     int ak = P("ak")!=0;
198     int an = P("an")!=0;
199     int ao = P("ao")!=0;
200     int ap = P("ap")!=0;
201     int aq = P("aq")!=0;
202     int ar = P("ar")!=0;
203     int as = g.okSetup && P("as")!=0;
204     int aw = P("aw")!=0;
205     if( as ) aa = 1;
206     if( aa ) ai = aw = ap = 1;
207     if( aw ) an = ar = 1;
208     if( ai ) ao = 1;
209     if( ak ) aj = 1;
210     if( aa ){ zCap[i++] = 'a'; }
211     if( ad ){ zCap[i++] = 'd'; }
212     if( ai ){ zCap[i++] = 'i'; }
213     if( aj ){ zCap[i++] = 'j'; }
214     if( ak ){ zCap[i++] = 'k'; }
215     if( an ){ zCap[i++] = 'n'; }
216     if( ao ){ zCap[i++] = 'o'; }
217     if( ap ){ zCap[i++] = 'p'; }
218     if( aq ){ zCap[i++] = 'q'; }
219     if( ar ){ zCap[i++] = 'r'; }
220     if( as ){ zCap[i++] = 's'; }
221     if( aw ){ zCap[i++] = 'w'; }
222 
223     zCap[i] = 0;
224     zOldPw = db_short_query("SELECT passwd FROM user WHERE id='%q'", zId);
225     db_execute("DELETE FROM user WHERE id='%q'", zId);
226     if( !P("delete2") ){
227       const char *zPw = P("pw");
228       char zBuf[3];
229       if( zOldPw==0 ){
230         char zSeed[100];
231         const char *z;
232         bprintf(zSeed,sizeof(zSeed),"%d%.20s",getpid(),zId);
233         z = crypt(zSeed, "aa");
234         zBuf[0] = z[2];
235         zBuf[1] = z[3];
236         zBuf[2] = 0;
237         zOldPw = zBuf;
238       }
239       db_execute(
240          "INSERT INTO user(id,name,email,passwd,capabilities) "
241          "VALUES('%q','%q','%q','%q','%s')",
242          zId, P("nm"), P("em"), zPw[0] ? crypt(zPw, zOldPw) : zOldPw, zCap
243       );
244     }else{
245       /* User was default assigned user id. Remove the default. */
246       db_execute( "DELETE FROM config WHERE "
247           "  name='assignto' AND value='%q'", zId);
248     }
249 
250     /*
251     ** The SCM subsystem may be able to replicate the user db somewhere...
252     */
253     if( g.scm.pxUserWrite ) g.scm.pxUserWrite(P("delete2")!=0 ? zId : 0);
254 
255     cgi_redirect("userlist");
256     return;
257   }
258 
259   /* Load the existing information about the user, if any
260   */
261   zName = "";
262   zEMail = "";
263   zCap = "";
264   oaa = oad = oai = oaj = oak = oan = oao = oap = oaq = oar = oas = oaw = "";
265   if( zId ){
266     azResult = db_query(
267       "SELECT name, email, capabilities FROM user WHERE id='%q'", zId
268     );
269     if( azResult && azResult[0] ){
270       zName = azResult[0];
271       zEMail = azResult[1];
272       zCap = azResult[2];
273       if( strchr(zCap, 'a') ) oaa = " checked";
274       if( strchr(zCap, 'd') ) oad = " checked";
275       if( strchr(zCap, 'i') ) oai = " checked";
276       if( strchr(zCap, 'j') ) oaj = " checked";
277       if( strchr(zCap, 'k') ) oak = " checked";
278       if( strchr(zCap, 'n') ) oan = " checked";
279       if( strchr(zCap, 'o') ) oao = " checked";
280       if( strchr(zCap, 'p') ) oap = " checked";
281       if( strchr(zCap, 'q') ) oaq = " checked";
282       if( strchr(zCap, 'r') ) oar = " checked";
283       if( strchr(zCap, 's') ) oas = " checked";
284       if( strchr(zCap, 'w') ) oaw = " checked";
285     }else{
286       zId = 0;
287     }
288   }
289 
290   /* Begin generating the page
291   */
292   common_standard_menu(0,0);
293   common_add_help_item("CvstracAdminUsers");
294   common_add_action_item("userlist", "Cancel");
295   common_add_action_item(mprintf("useredit?delete=1&id=%t",zId), "Delete");
296   if( zId ){
297     common_header("Edit User %s", zId);
298   }else{
299     common_header("Add New User");
300   }
301   @ <form action="%s(g.zPath)" method="POST">
302   @ <table align="left" hspace=10 vspace=10>
303   @ <tr>
304   @   <td align="right"><nobr>User ID:</nobr></td>
305   if( zId ){
306     @   <td>%h(zId) <input type="hidden" name="id" value="%h(zId)"></td>
307   }else{
308     @   <td><input type="text" name="id" size=10></td>
309   }
310   @ </tr>
311   @ <tr>
312   @   <td align="right"><nobr>Full Name:</nobr></td>
313   @   <td><input type="text" name="nm" value="%h(zName)"></td>
314   @ </tr>
315   @ <tr>
316   @   <td align="right"><nobr>E-Mail:</nobr></td>
317   @   <td><input type="text" name="em" value="%h(zEMail)"></td>
318   @ </tr>
319   @ <tr>
320   @   <td align="right" valign="top">Capabilities:</td>
321   @   <td>
322   @     <input type="checkbox" name="aa"%s(oaa)>Admin</input><br>
323   @     <input type="checkbox" name="ad"%s(oad)>Delete</input><br>
324   @     <input type="checkbox" name="ai"%s(oai)>Check-In</input><br>
325   @     <input type="checkbox" name="aj"%s(oaj)>Read Wiki</input><br>
326   @     <input type="checkbox" name="ak"%s(oak)>Write Wiki</input><br>
327   @     <input type="checkbox" name="an"%s(oan)>New Tkt</input><br>
328   @     <input type="checkbox" name="ao"%s(oao)>Check-Out</input><br>
329   @     <input type="checkbox" name="ap"%s(oap)>Password</input><br>
330   @     <input type="checkbox" name="aq"%s(oaq)>Query</input><br>
331   @     <input type="checkbox" name="ar"%s(oar)>Read</input><br>
332   if( g.okSetup ){
333     @     <input type="checkbox" name="as"%s(oas)>Setup</input><br>
334   }
335   @     <input type="checkbox" name="aw"%s(oaw)>Write</input>
336   @   </td>
337   @ </tr>
338   @ <tr>
339   @   <td align="right">Password:</td>
340   @   <td><input type="password" name="pw" value=""></td>
341   @ </tr>
342   if( !higherUser ){
343     @ <tr>
344     @   <td>&nbsp</td>
345     @   <td><input type="submit" name="submit" value="Apply Changes">
346     @       &nbsp;&nbsp;&nbsp;
347     @       <input type="submit" name="delete" value="Delete User"></td>
348     @ </tr>
349   }
350   @ </table>
351   @ <p><b>Notes:</b></p>
352   @ <ol>
353   if( higherUser ){
354     @ <li><p>
355     @ User %h(zId) has Setup privileges and you only have Admin privileges
356     @ so you are not permitted to make changes to %h(zId).
357     @ </p></li>
358     @
359   }
360   if( g.scm.pxUserWrite!=0
361         && !strcmp("yes",db_config("write_cvs_passwd","yes")) ){
362     @ <li><p>
363     @ If the <b>Check-out</b> capability is specified then
364     @ the password entered here will be used to regenerate the
365     @ <b>CVSROOT/passwd</b> file and will thus become the CVS password
366     @ as well as the password for this server.
367     @ </p></li>
368     @
369     @ <li><p>
370     @ The <b>Check-in</b> capability means that the user ID will be written
371     @ into the <b>CVSROOT/writers</b> file and thus allow write access to
372     @ the CVS repository.
373     @ </p></li>
374     @
375   }else{
376     @ <li><p>
377     @ If the <b>Check-out</b> capability is specified then the user will be able
378     @ to browse the %s(g.scm.zName) repository.
379     @ </p></li>
380     @
381     @ <li><p>
382     @ The <b>Check-in</b> capability gives the user the ability to edit check-in
383     @ messages.
384     @ </p></li>
385     @
386   }
387   @ <li><p>
388   @ The <b>Read</b> and <b>Write</b> privileges give the user the ability
389   @ to read and write tickets.  The <b>New Tkt</b> capability means that
390   @ the user is able to create new tickets.
391   @ </p></li>
392   @
393   @ <li><p>
394   @ The <b>Delete</b> privilege give the user the ability to erase
395   @ wiki, tickets, and atttachments that have been added by anonymous
396   @ users.  This capability is intended for deletion of spam.
397   @ </p></li>
398   @
399   @ <li><p>
400   @ The <b>Query</b> privilege allows the user to create or edit
401   @ report formats by specifying appropriate SQL.  Users can run
402   @ existing reports without the Query privilege.
403   @ </p></li>
404   @
405   @ <li><p>
406   @ An <b>Admin</b> user can add other users, create new ticket report
407   @ formats, and change system defaults.  But only the <b>Setup</b> user
408   @ is able to change the %h(g.scm.zName) repository to
409   @ which this program is linked.
410   @ </p></li>
411   @
412   if( zId==0 || strcmp(zId,"anonymous")==0 ){
413     @ <li><p>
414     @ No login is required for user "<b>anonymous</b>".  The capabilities
415     @ of this user are available to anyone without supplying a username or
416     @ password.  To disable anonymous access, make sure there is no user
417     @ with an ID of <b>anonymous</b>.
418     @ </p></li>
419     @
420     @ <li><p>
421     @ The password for the "<b>anonymous</b>" user is used for anonymous
422     @ %h(g.scm.zName) access.  The recommended value for the anonymous password
423     @ is "anonymous".
424     @ </p></li>
425   }
426   @ </form>
427   common_footer();
428 }
429 
430 /*
431 ** Remove the newline from the end of a string.
432 */
433 void remove_newline(char *z){
434   while( *z && *z!='\n' && *z!='\r' ){ z++; }
435   if( *z ){ *z = 0; }
436 }
437